Class ClassloadFilterAudit
SecurityPolicy.isSafeClassName(String) used to
enforce before D-12/D-13, and records what each removed layer WOULD have refused -- it refuses
nothing itself.
Package-private on purpose. This class holds the four lists SecurityPolicy used
to own directly (moved here once SecurityPolicy.addTrustedPackage(String) and
SecurityPolicy.addDangerousClass(String) became no-ops, so these now reflect exactly
what the framework as shipped would have judged, never a caller's runtime mutation). Same
package is the low-surface choice: the alternative would have been making the lists
public purely so a class outside utils could read them.
This evaluator never decides. classify(String) returns which layer would have
refused a class name, or null for none -- never a boolean verdict, and nothing here can
be used by a caller to refuse a class. It exists to answer, after release, "did removing these
layers actually let anything through?" from real data rather than from an assertion.
Cannot reuse RemoteActionLog. UltiTools.java's bootstrap runs the
classloading scan (initPluginModules()) before it constructs RemoteActionLog
(initWebSocketManagers()) -- the log does not exist yet at the point this evaluator
needs to run. Its Verdict is also ALLOWED/DENIED, and "would have been
denied under the old gate but is now unconditionally allowed" is neither. Logging follows the
same discipline RemoteActionLog and ModuleScanDiagnostics establish: a dedicated
non-root Logger with setUseParentHandlers(false), so a record here can never
reach SystemLogHandler (which attaches only to the root logger and auto-reports any
Level.SEVERE record carrying a Throwable into ErrorReportCollector) and
risk the circular-logging hazard both of those classes' javadoc already warns about.
setUseParentHandlers(false) also disconnects this logger from the root logger's own
handlers -- the console/logs/latest.log sink the server installs there -- so, exactly
like ModuleScanDiagnostics, a dedicated ConsoleHandler is attached directly in
the static initializer below to restore that reach without restoring the
SystemLogHandler path. Internal failures of this evaluator itself would print to
System.err rather than through any Logger -- the same discipline, for the same
reason -- but this class has no internal failure mode: classify(String) is a pure
function over in-memory Sets and never throws.
Locale.ROOT, not the default locale. The removed isSafeClassName lowercased with
the no-argument String#toLowerCase(), which uses the JVM's default locale. Under a
Turkish server locale, that method maps an ASCII capital I to a dotless ı, not a
dotted i -- silently changing which class names match a SUSPICIOUS_KEYWORDS
entry depending on where the server happens to run. This evaluator lowercases with
Locale.ROOT instead, so a Turkish-locale server produces the same audit as an
English-locale one. An audit whose result depends on the server's locale is not a measurement,
so that latent defect is corrected here rather than carried into the telemetry meant to replace
the removed enforcement.
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static enumThe four removed filter layers, in the exact orderisSafeClassNameevaluated them. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Logger(package private) static final LevelThe level of this class's ownConsoleHandler.One accumulator entry per module currently being scanned; reset when its summary emits. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescription(package private) static ClassloadFilterAudit.LayerClassifiesclassNameagainst the four removed layers, in the exact orderSecurityPolicy.isSafeClassNameevaluated them before D-12/D-13 (exact blacklist, dangerous package prefix, trusted-package whitelist, suspicious keyword), and returns the FIRST layer that would have refused it -- ornullif none would have.(package private) static voidemitSummary(String moduleName) Emits exactly ONELevel.INFOsummary formoduleName, ALWAYS -- even when nothing was recorded for it.private static boolean(package private) static voidRecords one class evaluated duringmoduleName's scan.
-
Field Details
-
AUDIT_LOGGER
-
CONSOLE_HANDLER_LEVEL
The level of this class's ownConsoleHandler. Named, package-private and asserted on byClassloadFilterAuditTestrather than left inline: the live handler list on a JUL logger is global mutable state that other tests in the same JVM add to and remove from, so a test that reads it back is order-dependent. This constant is the decision itself. -
SYSTEM_DANGEROUS_CLASSES
-
DANGEROUS_PACKAGE_PREFIXES
-
TRUSTED_PACKAGE_PREFIXES
-
SUSPICIOUS_KEYWORDS
-
COUNTS_BY_MODULE
One accumulator entry per module currently being scanned; reset when its summary emits.
-
-
Constructor Details
-
ClassloadFilterAudit
private ClassloadFilterAudit()
-
-
Method Details
-
classify
ClassifiesclassNameagainst the four removed layers, in the exact orderSecurityPolicy.isSafeClassNameevaluated them before D-12/D-13 (exact blacklist, dangerous package prefix, trusted-package whitelist, suspicious keyword), and returns the FIRST layer that would have refused it -- ornullif none would have.A
nullor blankclassNameis not a filter layer and always classifies tonull: the originalisSafeClassNamenull branch is now subsumed byClassLoaderUtils'sVALID_CLASS_NAME_PATTERNformat regex, which is the only thing that still rejects a malformed name (D-13).- Parameters:
className- the fully-qualified class name to classify, ornull/blank- Returns:
- the first layer that would have refused
className, ornull
-
record
Records one class evaluated duringmoduleName's scan. A no-op for anullor blankmoduleName, or whenclassify(String)finds no layer would have refusedclassName-- there is nothing interesting to record for a class none of the four removed layers would have touched.When a layer matches, increments that layer's per-module counter and logs the per-class detail at
Level.FINE-- matchingModuleScanDiagnostics'srecordSkippedClass/emitSummaryshape (D-19), so the two diagnostics read as one pattern rather than two.This method never decides anything: it has no boolean return, and
classNameis still handed to the real classloader by the caller regardless of what this method records (Test 10).- Parameters:
moduleName- the module (or scan unit) currently being evaluatedclassName- the class name being evaluated, ornull/blank
-
emitSummary
Emits exactly ONELevel.INFOsummary formoduleName, ALWAYS -- even when nothing was recorded for it. A module in which the removed layers would have refused nothing is itself the measurement (Test 8), not silence; this differs deliberately fromModuleScanDiagnostics.emitSummary, which is a no-op when its accumulator is empty. Resets the accumulator formoduleNameafterward, so a subsequent re-scan of the same module starts clean.- Parameters:
moduleName- the module whose scan just finished
-
isBlank
-