Class ClassloadFilterAudit

java.lang.Object
com.ultikits.ultitools.utils.ClassloadFilterAudit

final class ClassloadFilterAudit extends Object
GEN-07's observe-only classload filter audit (D-14): reproduces the exact matching semantics of the three name-based filter layers 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
  • Field Details

    • AUDIT_LOGGER

      private static final Logger AUDIT_LOGGER
    • CONSOLE_HANDLER_LEVEL

      static final Level CONSOLE_HANDLER_LEVEL
      The level of this class's own ConsoleHandler. Named, package-private and asserted on by ClassloadFilterAuditTest rather 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

      static final Set<String> SYSTEM_DANGEROUS_CLASSES
    • DANGEROUS_PACKAGE_PREFIXES

      static final Set<String> DANGEROUS_PACKAGE_PREFIXES
    • TRUSTED_PACKAGE_PREFIXES

      static final Set<String> TRUSTED_PACKAGE_PREFIXES
    • SUSPICIOUS_KEYWORDS

      static final Set<String> SUSPICIOUS_KEYWORDS
    • COUNTS_BY_MODULE

      private static final Map<String,int[]> COUNTS_BY_MODULE
      One accumulator entry per module currently being scanned; reset when its summary emits.
  • Constructor Details

    • ClassloadFilterAudit

      private ClassloadFilterAudit()
  • Method Details

    • classify

      static ClassloadFilterAudit.Layer classify(String className)
      Classifies className against the four removed layers, in the exact order SecurityPolicy.isSafeClassName evaluated 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 -- or null if none would have.

      A null or blank className is not a filter layer and always classifies to null: the original isSafeClassName null branch is now subsumed by ClassLoaderUtils's VALID_CLASS_NAME_PATTERN format regex, which is the only thing that still rejects a malformed name (D-13).

      Parameters:
      className - the fully-qualified class name to classify, or null/blank
      Returns:
      the first layer that would have refused className, or null
    • record

      static void record(String moduleName, String className)
      Records one class evaluated during moduleName's scan. A no-op for a null or blank moduleName, or when classify(String) finds no layer would have refused className -- 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 -- matching ModuleScanDiagnostics's recordSkippedClass/emitSummary shape (D-19), so the two diagnostics read as one pattern rather than two.

      This method never decides anything: it has no boolean return, and className is 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 evaluated
      className - the class name being evaluated, or null/blank
    • emitSummary

      static void emitSummary(String moduleName)
      Emits exactly ONE Level.INFO summary for moduleName, 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 from ModuleScanDiagnostics.emitSummary, which is a no-op when its accumulator is empty. Resets the accumulator for moduleName afterward, so a subsequent re-scan of the same module starts clean.
      Parameters:
      moduleName - the module whose scan just finished
    • isBlank

      private static boolean isBlank(String value)