Conversation
Extend the Velocity template sandbox's SecureIntrospector so method execution is denied on file, IO and network-resource type families. The check is hierarchy-aware (matching concrete platform implementations, e.g. the JDK's internal Path type, which an exact-string class/package list cannot reach) and covers File, Path, InputStream, OutputStream, Reader, Writer, Channel, FileSystem, FileSystemProvider, URL and URI, plus the Files and Paths utility holders by identity. The existing Number/Boolean/String and Class.getName() fast-paths and the prior restricted classes are preserved. The restricted-class list in velocity.properties is updated in parallel to document the same network-resource and file-system utility types alongside the existing entries; the code-level check is the enforcement floor. Adds a unit contract test for the introspector decision and a render-time integration test proving the guard is applied by the engine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @nollymar's task in 2m 20s —— View job Code Review — SecureIntrospector hardening
The second commit ( New IssuesNo blocking issues found. The implementation is correct:
Medium (non-blocking)
Notes
Nice work — this is a clean, well-tested hardening PR that also folds in the reviewer-requested configurability. |
|
Are there any performance implications of this change? Reflection is already Velocity's weakpoint. It would be interesting to do a before and after perf test. |
|
dotbot code review:
The hierarchy-aware restricted-type check is correctly ordered after the existing allow fast-paths and the ClassLoader/Thread deny, so it neither weakens existing allows nor is shadowed. Tests cover unit-level denial, value-type allowances, config sync, and render-time wiring, and the new integration test is registered in MainSuite1b. No bugs introduced by this patch were identified. Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads. reviewed by dotbot · ~z-ai/glm-latest · medium |
dotCMS-Machine-User
left a comment
There was a problem hiding this comment.
✅ dotbot review: all reviewer models (meta/muse-spark-1.3, ~z-ai/glm-latest) agree — patch is correct.
approved automatically by dotbot
|
Good question @wezell — net impact is negligible, and I measured it. Where the check runs. Method resolution goes through Velocity's per-context introspection cache ( Cost of the check itself. The added coverage is a bounded set of Before/after microbenchmark of
So the worst case adds single-digit nanoseconds per distinct method resolution, paid once per render and cached after. A render resolving ~100 distinct signatures pays well under 1 µs total, one time — below the noise of parsing / I/O / DB. Happy to run a full render-level benchmark end-to-end if you'd like to see it, but at the introspection level the delta is in the nanoseconds and partly negative. |
| introspector.restrict.classes = java.net.URL | ||
| introspector.restrict.classes = java.net.URI | ||
| introspector.restrict.classes = java.nio.file.Files | ||
| introspector.restrict.classes = java.nio.file.Paths |
There was a problem hiding this comment.
why not go deeper here and include other packages?
java.nio
java.net
Also, can we configure (env var) packages to include or exclude? that would be handy and would allow us to push the fix deeper to a more white listed stance. We update these deny lists all the time as new holes are discovered.
There was a problem hiding this comment.
My opinion. Switch it to an allow list and add the default deny on the fall through.
There was a problem hiding this comment.
I think that could break some implementations but that is where I was suggesting to move, as long as it is configurable and we log the deny message
There was a problem hiding this comment.
Good call — made it configurable. Pushed a follow-up that adds an operator-configurable layer on top of the code-level (type-hierarchy) floor, read via dotCMS Config so it's env-overridable and updatable without a code change:
velocity.introspector.restrict.packages(envDOT_VELOCITY_INTROSPECTOR_RESTRICT_PACKAGES) — comma-separated package prefixes to deny, sojava.nio,java.netalso coversjava.nio.file,java.net.http, etc.velocity.introspector.restrict.classes(envDOT_VELOCITY_INTROSPECTOR_RESTRICT_CLASSES) — exact class names.velocity.introspector.allow.classes(envDOT_VELOCITY_INTROSPECTOR_ALLOW_CLASSES) — carve specific classes back out of the configurable denial.
This gives us the deeper, whitelist-leaning stance you're after: set the restrict packages to java.nio,java.net (and beyond) per environment, and exempt the few classes a template legitimately needs via the allow-list. Denials are already logged in getMethod() ("... due to security restrictions").
On your point that broadening could break implementations — agreed, which is exactly why I left the defaults empty rather than denying all of java.nio/java.net out of the box: a blanket default would break common template usage (InetAddress, URLEncoder, ByteBuffer, charsets, etc.). So the aggressive stance is opt-in per environment, and the hardcoded type-hierarchy floor (File/Path/streams/URL/URI + concrete impls) stays as the always-on baseline that config can extend but not weaken.
One deliberate boundary: the allow-list carves classes out of the configurable layer only — it can't re-open the hardcoded floor or the existing reflection/system denials, so a config change can't accidentally undo the core fix. Easy to revisit if you'd rather it be fully override-able.
Covered by a unit test (package-prefix denial + allow-list carve-out). Happy to seed a stricter default set (e.g. deny java.nio/java.net with a curated allow-list) if you'd prefer that shipped on by default.
…perator-configurable Adds an operator-configurable layer to SecureIntrospector, additive to the code-level type-hierarchy floor, so deployments can push the template sandbox toward a stricter, package-level (prefix) stance without a code change as new gaps are found: - velocity.introspector.restrict.packages (env DOT_VELOCITY_INTROSPECTOR_RESTRICT_PACKAGES) comma-separated package prefixes to deny (e.g. "java.nio,java.net") - velocity.introspector.restrict.classes (env DOT_VELOCITY_INTROSPECTOR_RESTRICT_CLASSES) comma-separated exact class names to deny - velocity.introspector.allow.classes (env DOT_VELOCITY_INTROSPECTOR_ALLOW_CLASSES) comma-separated exact class names carved back out of the configurable denial Read via dotCMS Config (env-overridable) with empty defaults, so behavior is unchanged unless configured; the code-level floor and existing denials are never weakened by the allow-list. Denials continue to be logged by getMethod(). Adds a unit test covering the package-prefix denial and the allow-list carve-out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
dotbot code review:
Hierarchy-aware deny is correctly ordered after existing allows and covers concrete subtypes, with Files/Paths handled by identity. Config layer is additive-only with empty defaults and cannot weaken the code-level floor. Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads. reviewed by dotbot · meta/muse-spark-1.3 · medium |
| final String[] values = Config.getStringArrayProperty(prop, EMPTY); | ||
| return values == null ? EMPTY : values; | ||
| } | ||
| catch (final Throwable t) |
There was a problem hiding this comment.
⚪ [P3] SecureIntrospectorImpl.java:241 swallows all Throwables when reading Config
Current code:
catch (final Throwable t)
{
return EMPTY;
}Problem: Catching Throwable without logging silently disables the operator deny layer if Config misbehaves (e.g. system-table lookup throws).
Fix:
catch (final Exception e)
{
Logger.debug(SecureIntrospectorImpl.class,
"Unable to read Velocity introspector config " + prop + ": " + e.getMessage());
return EMPTY;
}| */ | ||
| private static final Class<?>[] RESTRICTED_SUPERTYPES = { | ||
| File.class, Path.class, RandomAccessFile.class, InputStream.class, | ||
| OutputStream.class, Reader.class, Writer.class, Channel.class, |
There was a problem hiding this comment.
⚪ [P3] SecureIntrospectorImpl.java:294 blanket IO/URL deny also hits context-provided writers and URLs
Current code:
OutputStream.class, Reader.class, Writer.class, Channel.class,
FileSystem.class, FileSystemProvider.class, URL.class, URI.classProblem: Deny is by supertype, so every method on any Writer/Reader/URL/URI reachable from the context is now inert — e.g. $response.getWriter().println(...) or $url.getPath() — not just the file/IO methods.
Assumption: customer VTL may call such methods.
What to verify: grep customer templates for getWriter, getInputStream, getPath()/getQuery() on URL/URI values; the new velocity.introspector.allow.classes is the escape hatch if so.
|
dotbot code review:
The hierarchy-aware deny is correctly ordered after the allow fast-paths and the ClassLoader/Thread deny, so it only broadens denials; the configurable layer defaults to empty and can never weaken the code-level floor. No provable P0/P1 defect was found; the two items raised are low-priority robustness/compatibility concerns. Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads. reviewed by dotbot · deepseek/deepseek-v4.1-flash · medium |
Fixes dotCMS/private-issues#704
What
Extends the Velocity template sandbox's
SecureIntrospectorso method execution is denied on the file, IO, and network-resource type families. The introspector previously matched restricted classes and packages by exact string, so types outside the enumeratedjava.lang.*/ reflection set — includingjava.io.File,java.nio.file.Path, IO streams, andjava.net.URL/URI— were not covered.Changes
SecureIntrospectorImpl: adds a hierarchy-aware restricted-type check alongside the existingClassLoader/Threadblock. Matching by type hierarchy means concrete platform implementations (for example the JDK's internalPathtype) are covered as well, which an exact-string list cannot reach. Covered families:File,Path,RandomAccessFile,InputStream,OutputStream,Reader,Writer,Channel,FileSystem,FileSystemProvider,URL,URI, plus theFilesandPathsutility holders by identity.velocity.properties: documents the same network-resource and file-system utility types in the restricted-class list, alongside the existing entries. The code-level check is the enforcement floor.Number/Boolean/StringandClass.getName()fast-paths and all previously restricted classes are preserved unchanged.@Overrideadded togetMethodandcheckObjectExecutePermission.Testing
SecureIntrospectorImplTest): asserts the file/IO/network-resource families (including a concrete platformPathimplementation) are denied, the previously restricted reflection/system families stay denied, and the common value/collection types stay allowed. Also verifies the config list enumerates the exact-matchable resource types.SecureIntrospectorRenderTest, registered inMainSuite1b): renders templates through the real Velocity engine and confirms the guard is applied at render time, while ordinary value methods still render.Both suites pass locally (
Tests run: 5unit,Tests run: 3integration).🤖 Generated with Claude Code