Repository navigation
Conversation
&{'key', args} printed Messages.get(key, args) raw, so String arguments
were not HTML-escaped in .html templates, unlike ${}. Escape each
argument in ExecutableTemplate.__getMessage() using the same rule as
__safe(): only in .html templates and not inside #{verbatim}.
The message text is left unescaped. RawData (arg.raw()), null, numbers,
booleans, characters and dates pass through so %d/%f/%c/%t formats keep
working. Any other object is escaped through toString(), like ${}.
HarriFent
marked this pull request as ready for review
November 5, 2026 18:23
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
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.
Fixes
Fixes #1038
Purpose
In an
.htmltemplate,${name}is HTML-escaped but&{'key', name}is not. The arguments go intoMessages.get(key, args)and the result is printed raw. So any user-set value passed as a message argument is a stored XSS:&{'welcome', user.name} <!-- user.name = <script>alert(1)</script> runs -->This PR escapes the arguments in
ExecutableTemplate.__getMessage(), using the same rule as__safe():.htmltemplates, and not inside#{verbatim}.HTML.htmlEscape(arg.toString()), the same as${arg}.Please <a href="...">register</a>), and they are written by the developer.RawData(soarg.raw()opts out),null,Number,Boolean,Character,Date,Calendarandjava.time.temporal.TemporalAccessor. This keeps%d,%f,%cand%tformats working..txt,.xml,.jsonand other templates are unchanged.The docs section on
&{…}intemplates.textilenow says this.Background Context
Choices:
toString(), not onlyString/CharSequence. That matches${obj}and also covers GStrings and entities whosetoString()returns user data.__safeFaster()also has XML and CSV formatters, but the CSV one quotes the whole value, which would be wrong for a fragment inside a message.&{other.key}is no longer expanded as a message reference, because the&is escaped. Before, user input could trigger message lookups.Migration note
If a template passes HTML on purpose as a message argument, wrap it in
raw():&{'welcome', user.name, link.raw()}Arguments used as nested message keys (
key=%d &{%s}) are escaped before lookup. So a key containing<,>,&,"or'would no longer match. Wrap that argument inraw()if that applies.Release notes
Security:
&{'key', args}in HTML templates now HTML-escapes its arguments like${}; the message text is unchanged. Wrap arguments that hold intentional HTML in.raw().Tests
New
GroovyTemplateMessageArgsTestcovers: String and GString args escaped, other objects escaped like${},raw()andRawDatauntouched, message text not escaped,%d/%tYwith numbers, numeric strings,Calendar,Dateandnull,&{…}inside an argument not expanded,.txtnot escaped,#{verbatim}not escaped.Ran on JDK 21 (macOS):
ant clean jar unittest -Dbuild-release=21: all 257 unit tests pass. On master the new test fails 6 of 9; the three "not escaped" cases pass on both.ant test -Dbuild-release=21: passes, including the sample apps whose CRUD views use&{'crud.…', type.modelName}.References