Jinjava 3.0: Add method and return type validator framework#1297
Draft
jasmith-hs wants to merge 5 commits into3.0/test-objectsfrom
Draft
Jinjava 3.0: Add method and return type validator framework#1297jasmith-hs wants to merge 5 commits into3.0/test-objectsfrom
jasmith-hs wants to merge 5 commits into3.0/test-objectsfrom
Conversation
Update some tests Implement allowlisting for methods and classes Extract separate MethodValidator Add result validating Use string-based config. Make the allowlists easier to work with Wrap classes before validating result Split MethodValidator from ReturnTypeValidator Don't create new JinjavaBeanELResolver instances per Jinjava or JinjavaConfig for bean cache performance Validate ReturnTypes at the top-level so that accessing values of arrays, lists, maps have their return values validated Wrap at a higher level and don't resolve `____int3rpr3t3r____` Use BeanMethods and cache allowed methods and return types Don't expect ____int3rpr3t3r____ and don't use arrays in ReverseFilter and add method and return type validator to test classes Helper for constructing JinjavaConfig in tests Extract test objects to separate classes. Allow arrays. Add AnnotationIntrospector Don't need annotation introspector All tests passing Fix build Describe a couple more changes Don't allow jinjava constructs outside of the AllowlistGroup classes from being allowlisted Add tests that certain classes and packages CANNOT be allowlisted Allow BigInteger Add annotation to MethodValidator Test that certain constructs are fully banned Remove unused method from ReturnTypeValidator Use Map and Set instead of ImmutableMap and ImmutableSet for less churn Apply spotless formatting Fix test after merge conflicts Remove 3.0-changes file
- Remove stale commented-out line in ExpressionResolver - Remove @Value.Check from static utility method in BannedAllowlistOptions - Make BeanMethod.method volatile for safe publication across threads - Guard against getBeanMethods returning null for unknown method names - Guard against getCanonicalName returning null for anonymous/local classes in AllowlistReturnTypeValidator and AllowlistMethodValidator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use instanceof pattern matching in NoInvokeELContext to avoid bare ClassCastException when delegate doesn't implement HasInterpreter - Guard against null JinjavaInterpreter.getCurrent() in SizeLimitingPySet.checkSize Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1288.
Introduces an allowlist-based validator framework that controls which Java methods can be invoked from Jinja templates and what return types are permitted. This replaces the old blocklist approach (
getRestrictedMethods/getRestrictedProperties/isRestrictedClass) with an opt-in model: only explicitly-permitted methods and return types are allowed through.Why this matters
The old security model in
JinjavaBeanELResolverworked by blocking known-bad things: a hardcoded set of method names (class,clone,hashCode,getClass, etc.), anisRestrictedClasscheck for dangerous types (Class,ClassLoader,Thread,Method,java.lang.reflect.*,com.fasterxml.jackson.databind.*), and optional per-config restricted method/property sets.Blocklists are fundamentally fragile — they require anticipating every dangerous thing. An allowlist inverts the model: unknown things are rejected by default, and only types explicitly categorised as safe are permitted.
New components
Validator interfaces and config
MethodValidator— interface with a singlevalidateMethod(Method)that returnsnullto reject or the method to accept. Supports chaining additional validators.ReturnTypeValidator— same pattern but for return values:validateReturnType(Object).MethodValidatorConfig(@Value.Immutable) — specifies which methods are allowed, by exactMethodinstance, by declaring class canonical name, or by class name prefix. Has anonRejectedMethodcallback for observability. Built viaMethodValidatorConfig.builder().ReturnTypeValidatorConfig(@Value.Immutable) — specifies which return types are allowed, by exact canonical class name or by prefix. Has anonRejectedClasscallback and anallowArraysflag (only enabled for theCollectionsgroup). Built viaReturnTypeValidatorConfig.builder().Both configs enforce hard safety guarantees via
@Value.Check: attempting to addjava.lang.Object,java.lang.Class,java.lang.reflect.*,com.fasterxml.jackson.databind.*, or anycom.hubspot.jinjava.*type outside the explicit safe groups throwsIllegalStateExceptionat construction time. Misconfiguration is a hard error, not a silent runtime bypass. This logic lives inBannedAllowlistOptions.AllowlistGroup enum
Defines named semantic groups used to populate configs:
JavaPrimitivesString,BigDecimal,BigIntegerJinjavaObjectsPyList,PyMap,SizeLimitingPy*,PyishDate,FormattedDate,DummyObject,Namespace,SafeString,NullValueCollectionsPySet,SizeLimitingPySet,ArrayList, Guava forwarding collections,LinkedHashMap; also enables array return typesJinjavaTagConstructsForLoop,MacroFunction,EagerMacroFunctionJinjavaFilterscom.hubspot.jinjava.lib.filter(by package prefix)JinjavaFunctionsZonedDateTime(used by date functions)JinjavaExpTestscom.hubspot.jinjava.lib.exptest(by package prefix)AllowlistMethodValidator.DEFAULTandAllowlistReturnTypeValidator.DEFAULTare pre-built from all groups and used as the defaults inJinjavaConfig.AllowlistMethodValidator / AllowlistReturnTypeValidator
Both validators use
ConcurrentHashMapcaches (keyed onMethodor canonical class name) so the allowlist check is only evaluated once per unique method/type during a JVM session. TheonRejected*callback fires for observability before the method/value is suppressed.ReturnTypeValidatingJinjavaInterpreterResolver
A thin
ELResolverdecorator that wrapsJinjavaInterpreterResolver. AllgetValueandinvokecalls are passed throughAllowlistReturnTypeValidator.validateReturnType()after resolution.ExpressionResolvernow builds this wrapper instead of usingJinjavaInterpreterResolverdirectly.HasInterpreter
A small interface (
JinjavaInterpreter interpreter()) implemented byJinjavaELContextandNoInvokeELContext. Allows the EL resolver layer to obtain the current interpreter from the context without a staticgetCurrent()call when the context is available.Changes to existing components
JinjavaBeanELResolver
The central change. The old
DEFAULT_RESTRICTED_METHODS,DEFAULT_RESTRICTED_PROPERTIES, andisRestrictedClassare removed entirely. Method validation is now delegated:findMethod— after resolving the candidate method, callsgetJinjavaConfig().getMethodValidator().validateMethod(method). Returnsnull(method not found) if the validator rejects it.getReadMethod/getWriteMethod— same pattern, so property access also goes through the allowlist.coerceValue— new override replaces the old____int3rpr3t3r____hack. When a method parameter is typedJinjavaInterpreter, it injectsJinjavaInterpreter.getCurrent()directly. Filters and expression tests that acceptJinjavaInterpreteras a parameter continue to work without any special naming convention.BeanMethodscache (Introspector.getBeanInfo→MethodDescriptor[]indexed by name) per class, stored in aConcurrentHashMap<Class<?>, BeanMethods>. This avoids callinggetClass().getMethods()on every invocation and removes thefindAccessibleMethodscan from the hot path.JinjavaConfig
getRestrictedMethods()andgetRestrictedProperties().getMethodValidator()(default:AllowlistMethodValidator.DEFAULT).getReturnTypeValidator()(default:AllowlistReturnTypeValidator.DEFAULT).Both new methods have
@Value.Defaultso existingJinjavaConfigbuilders continue to work without changes.JinjavaInterpreterResolver
Raw
Setvalues are now wrapped inSizeLimitingPySet(analogous to howListandMapare already wrapped in theirSizeLimiting*equivalents). This also meansSetis now in theCollectionsallowlist group and can be returned from expressions.PySet / SizeLimitingPySet
PySet— a newForwardingSet<Object>that implementsPyWrapper. Includes a recursion-guardedhashCode()to handle sets that contain themselves.SizeLimitingPySet— mirrorsSizeLimitingPyList/SizeLimitingPyMap. EnforcesmaxSizeon construction,add, andaddAll; emits aWARNING-levelTemplateErrorwhen the set reaches 90% capacity.ArrayBacked
Marker interface (
T[] backingArray()) for types that expose a backing array, used to support array return types through the validator.Tests
ValidatorConfigBannedConstructsTest— verifies that banned types (Object,Class,java.lang.reflect,com.fasterxml.jackson.databind,JinjavaInterpreter) cannot be added to eitherMethodValidatorConfigorReturnTypeValidatorConfigwithout throwing.JinjavaBeanELResolverTest— restructured to test the new allowlist-delegation behavior rather than the removed hardcoded blocklist.BaseJinjavaTestand most other tests updated to use explicit validator config where tests exercise method calls that go through the allowlist.