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

Reply via email to