This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a commit to branch wicket-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 75ff31e14454474a5864ff036f7db24ebac9f74c
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 1716aae0b1..aa959a1902 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -242,6 +242,55 @@ protected against modification depends entirely on the 
implementation in use,
 and the default implementation is not authenticated. Consult the javadoc of the
 `ICrypter` you configure and rely on no more than it states.
 
+### 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 `ResourceIsolationRequestCycleListener` is registered, a request 
originating

Reply via email to