This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch feature/WW-5695-html5-constraint-validation in repository https://gitbox.apache.org/repos/asf/struts.git
commit 07dbf7b35121d77cf35d5600eaf372fa392a10fb Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Aug 24 20:26:31 2026 +0200 WW-5695 fix(validation): drop \s and \S from the ECMAScript-safe escape allowlist A task reviewer found a real semantic divergence the design had certified as portable. Java's \s is ASCII-only by default; ECMAScript's is always the wider Unicode set. For a rule as ordinary as ^\S+$, a value containing NBSP satisfies Java's \S and fails the browser's — so the server accepts input the browser silently refuses to submit, which is exactly the failure this allowlist exists to prevent. \d and \w are safe: both engines are ASCII-only for those by default and JavaScript never widens them, so the fix is scoped to s/S. The spec listed \s among the safe escapes and the plan's ALLOWED_ESCAPES transcribed it. Both corrected, plus a regression test. The governing rule outranks its own example list. Co-Authored-By: Claude Opus 5 <[email protected]> --- .../plans/2026-08-24-html5-constraint-validation.md | 21 ++++++++++++++++++--- ...2026-08-24-html5-constraint-validation-design.md | 10 +++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-08-24-html5-constraint-validation.md b/docs/superpowers/plans/2026-08-24-html5-constraint-validation.md index aa8619535..f174acaa5 100644 --- a/docs/superpowers/plans/2026-08-24-html5-constraint-validation.md +++ b/docs/superpowers/plans/2026-08-24-html5-constraint-validation.md @@ -345,6 +345,14 @@ public class EcmaScriptSafeRegexTest { assertThat(EcmaScriptSafeRegex.isSafe("\\h+")).isFalse(); } + @Test + public void rejectsWhitespaceClassesWhoseMeaningDiffersBetweenEngines() { + // Java's \s is ASCII-only by default; ECMAScript's includes NBSP and friends, so + // ^\S+$ accepts a value containing NBSP on the server and rejects it in the browser + assertThat(EcmaScriptSafeRegex.isSafe("^\\S+$")).isFalse(); + assertThat(EcmaScriptSafeRegex.isSafe("\\s*")).isFalse(); + } + @Test public void rejectsPossessiveQuantifiers() { assertThat(EcmaScriptSafeRegex.isSafe("\\d++")).isFalse(); @@ -402,8 +410,15 @@ package org.apache.struts2.components; */ public final class EcmaScriptSafeRegex { - /** Escapes with identical meaning in both engines. */ - private static final String ALLOWED_ESCAPES = "dDwWsSbBnrtf\\.*+?()[]{}|^$/-"; + /** + * Escapes with identical meaning in both engines. + * <p> + * {@code \s} and {@code \S} are deliberately absent. Java's {@code \s} is ASCII-only by default + * while ECMAScript's is the wider Unicode set, so {@code ^\S+$} accepts a value containing NBSP + * on the server and rejects it in the browser. {@code \d} and {@code \w} are safe — both engines + * are ASCII-only for those, and JavaScript never widens them. + */ + private static final String ALLOWED_ESCAPES = "dDwWbBnrtf\\.*+?()[]{}|^$/-"; private EcmaScriptSafeRegex() { } @@ -471,7 +486,7 @@ public final class EcmaScriptSafeRegex { - [ ] **Step 4: Run the test to verify it passes** Run: `mvn test -DskipAssembly -pl core -Dtest=EcmaScriptSafeRegexTest` -Expected: PASS, 6 tests. +Expected: PASS, 7 tests. - [ ] **Step 5: Commit** diff --git a/docs/superpowers/specs/2026-08-24-html5-constraint-validation-design.md b/docs/superpowers/specs/2026-08-24-html5-constraint-validation-design.md index 234999b0b..8e7c5c7e3 100644 --- a/docs/superpowers/specs/2026-08-24-html5-constraint-validation-design.md +++ b/docs/superpowers/specs/2026-08-24-html5-constraint-validation-design.md @@ -119,10 +119,18 @@ This is the least-solved part of the design and the strongest argument for the p A denylist of Java-only constructs (`\p{Alpha}`, possessive quantifiers, `\A`/`\z`, lookbehind) violates the never-false-reject rule the first time it misses one: a missed construct becomes a `pattern` the browser -interprets differently. So detection is an **allowlist** — literals, `\d \w \s` and their negations, +interprets differently. So detection is an **allowlist** — literals, `\d` and `\w` and their negations, character classes without POSIX or Unicode property syntax, grouping, alternation, anchors, and bounded quantifiers. Anything outside it emits no `pattern`. +**`\s` and `\S` are excluded, and this is the rule's first real test.** Java's `\s` is ASCII-only by default +(`[ \t\n\x0B\f\r]`); ECMAScript's is always the wider Unicode set — NBSP, ``, ` `, the ` ` +range. For a rule as ordinary as `^\S+$`, a value containing NBSP satisfies Java's `\S` and fails the +browser's, so the server would accept input the browser silently refuses to submit. `\d` and `\w` are safe: +both engines are ASCII-only for those by default, and JavaScript never widens them. An earlier draft of this +spec listed `\s` among the safe escapes — that was wrong, and the governing rule outranks its own example +list. + This is conservative to the point that some legitimate regexes will silently get no client-side check. That is the correct failure direction under the agreed rule, and it is the piece most likely to need tuning after real use.
