Conversation
…t, depth, or size bound checkCyclicSubstitution() only rejects a variable already on the current substitution stack. It does not bound acyclic fan-out (each of N references expanding to N more) or deep nesting, so a crafted variable map can drive interpolation into exponential output growth or a StackOverflowError without any variable repeating on the stack. Bound substitute() with a maximum interpolation depth (256) and a maximum total output size (16 MiB) per top-level substitution, both raising IllegalStateException. Mirrors the fix for the deprecated commons-lang3 StrSubstitutor (c8dc3121).
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.
This ports the recursion bound applied to the deprecated commons-lang3 StrSubstitutor (apache/commons-lang c8dc3121) over to StringSubstitutor, as suggested in the security-report thread ("Then please provide a PR on GitHub").
Problem
StringSubstitutor's checkCyclicSubstitution() only rejects a variable already on the current substitution stack. It does not bound:
A crafted variable map (where the substitution values are attacker-controlled) can therefore drive interpolation into exponential output growth (CPU/memory exhaustion) or a stack overflow.
Fix
Two budgets per top-level substitution, matching the lang3 fix:
Both raise IllegalStateException when exceeded. substitute(...) is split into a small budget-enforcing wrapper plus the existing recursive body (substituteRecursive), so the counters cover value recursion and nested-name resolution alike and reset per top-level call — a budget-exceeded failure does not poison the next replace().
No public API change; behavior is unchanged for any input under the budgets.
Tests
Two regression tests added to StringSubstitutorTest (deep nesting → depth budget; exponential fan-out → size budget). The full StringSubstitutorTest suite passes (81 tests).
Before you push a pull request, review this list:
Yes. I used Claude (Anthropic). It is a port of the maintainer's own commons-lang3 fix (c8dc3121) to StringSubstitutor; Claude wrote the two unit tests, and drafted this description under my direction. I reviewed the result, confirmed the tests fail without the runtime change, and I take responsibility for the contribution.
mvn; that'smvnon the command line by itself.