This is an automated email from the ASF dual-hosted git repository. papegaaij pushed a commit to branch wicket-8.x in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 629b79fbcb353ce4c146f5023d30fe19fdd10446 Author: Emond Papegaaij <[email protected]> AuthorDate: Wed Aug 19 22:26:29 2026 +0200 Document how model data, markup and message bundles are escaped The security model said which inputs Wicket trusts and nothing about what it does with them on the way out, so there was no written statement of the escaping contract to measure a report against. It now says that escapeModelStrings is that contract and is on by default, that clearing it hands the responsibility to the application, and that a component writing model data to the markup unescaped in the default configuration is a bug in the framework. It also draws the line on the two inputs that are trusted, since both are authored by the developer rather than received at runtime: markup files, and message bundles, where wicket:message renders its value as markup unless the tag asks for escaping. The value a bundle string interpolates is not covered by that, because it is resolved from the surrounding component's model. Finally it states what Strings#escapeMarkup is for. It escapes the five characters that matter in element text and in a quoted attribute value, and it is not enough anywhere else: not inside a script or style element, not in an unquoted attribute, and not where a url scheme is the payload. Wicket does not escape for a JavaScript context anywhere, so a value an application puts in one, through TextTemplate substitution for instance, has to be encoded by the application. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- SECURITY.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index ffc7a91a2c..fd3239f80a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -253,6 +253,55 @@ at must therefore be storage only the application itself can read and write. The encrypted page store, and the trust statement that goes with it, exist on 9.x and 10.x only. +### Model data is escaped; markup and message bundles are trusted + +Wicket escapes the text a component renders from its model. `Component`'s +`escapeModelStrings` flag is **on by default**, and a component renders +model-derived text either through `Component#getDefaultModelObjectAsString()` or +by applying `Strings#escapeMarkup` when that flag is set. A component that writes +application model data into the markup unescaped in the default configuration is +a bug in the framework and an opening for cross-site scripting (XSS). We want to +hear about it. + +`setEscapeModelStrings(false)` is the application saying the content is markup +and taking responsibility for it. Reports that depend on an application having +cleared the flag are configuration issues rather than framework vulnerabilities. +Note that a few components clear it themselves because their value is written +into an attribute, which is escaped when the tag is written and would otherwise +be encoded twice; that is an implementation detail of those components and not an +invitation to render untrusted markup through them. + +Two inputs on the other side of the boundary are trusted, because both are +authored by the developer and neither is data the application received at +runtime: + +- **Markup files are trusted.** A `.html` file on the classpath is a template, + exactly like a JSP or a Thymeleaf template, and Wicket renders it as markup. An + application that serves markup from somewhere an untrusted party can write — + through a custom `IMarkupResourceStreamProvider`, for instance — has taken + that trust on itself. +- **Message bundles are trusted.** `<wicket:message key="…"/>` renders its + property value as markup by default, and `escape="true"` opts in to escaping. + Markup in a bundle is therefore a supported way to format a message. + +The value a bundle string interpolates is a different matter. `${name}` in a +message resolves first to a child component with `wicket:id="name"`, whose +rendered markup carries that component's own escaping. Only when there is no +such child does Wicket fall back to reading `name` from the surrounding +component's model, and that value is written as it came — so a static bundle can +still place model data in the markup unescaped. Prefer the child component. +Where the fallback is unavoidable and the data is not trusted, the message needs +`escape="true"`, which escapes the whole message and therefore any markup the +bundle itself contains. + +Finally, `Strings#escapeMarkup` escapes `<`, `>`, `&`, `"` and `'`. That is +enough for element text and for a quoted attribute value, and it is not enough +for anything else: it does not make a value safe inside `<script>` or `<style>`, +in an unquoted attribute, or in a URL where the scheme itself is the payload. +Wicket does not escape for a JavaScript context anywhere, so a value the +application places in one — through `TextTemplate` variable substitution, for +example — has to be encoded by the application. + ### Another origin may not invoke a listener Where `CsrfPreventionRequestCycleListener` is registered, a request originating
