From 5b175a1dad7ee17bc8d04e7c7deaf38fc0f8e510 Mon Sep 17 00:00:00 2001 From: "hivecommons-hive[bot]" Date: Fri, 25 Sep 2026 22:00:22 -0400 Subject: [PATCH] test(svg-active-content): cover single-quoted and unquoted attribute values ATTRIBUTE_PATTERN in scripts/lib/svg-active-content.mjs accepts three attribute value forms -- double-quoted, single-quoted and unquoted -- and findActiveContent and stripActiveContent read them through the fallback chains 'match[2] ?? match[3] ?? match[4]' and 'dq ?? sq ?? uq'. Every existing case wrote double-quoted markup, so only the first alternative of either chain ran: a regression that dropped an alternative from the pattern, or read the wrong capture group, would leave the suite green while onclick='alert(1)' and href=javascript:alert(1) passed the sanitizer unseen. Add quoting variants for the three attribute classes the scanner recognises -- event handler, script URI, embedded document -- plus an inert-value case proving the pattern does not over-match. Region coverage for scripts/lib/svg-active-content.mjs rises 93.18% -> 97.73% and src files 97.95% -> 98.20%. The two remaining '?? ""' tails are unreachable, since the regex cannot match with all three capture groups undefined. Signed-off-by: hivecommons-hive[bot] --- tests/svg-active-content.test.mjs | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/svg-active-content.test.mjs b/tests/svg-active-content.test.mjs index 7b852770..142dbaab 100644 --- a/tests/svg-active-content.test.mjs +++ b/tests/svg-active-content.test.mjs @@ -341,3 +341,62 @@ test('leaves foreignObject itself untouched', () => { assert.deepEqual(findActiveContent(svg), []); assert.deepEqual(stripActiveContent(svg), { source: svg, removed: [] }); }); + +// ATTRIBUTE_PATTERN accepts three value forms -- double-quoted, single-quoted +// and unquoted -- and findActiveContent/stripActiveContent read them through +// `match[2] ?? match[3] ?? match[4]` and `dq ?? sq ?? uq`. Every case above +// writes double-quoted markup, so only the first alternative of either +// fallback chain was ever exercised: a regression that dropped the +// single-quoted or unquoted alternative from the pattern, or read the wrong +// capture group, would have left the suite green while every +// `onclick='alert(1)'` and `href=javascript:alert(1)` an SVG editor emits +// passed the gate unseen. Browsers accept all three forms identically. +const QUOTING_VARIANTS = [ + { article: 'a', label: 'single-quoted', quote: "'" }, + { article: 'an', label: 'unquoted', quote: '' }, +]; + +for (const { article, label, quote } of QUOTING_VARIANTS) { + test(`detects and removes ${article} ${label} event handler attribute`, () => { + const svg = ``; + assert.deepEqual(findActiveContent(svg), [ + 'contains event handler attribute(s): onclick', + ]); + + const { source, removed } = stripActiveContent(svg); + assert.doesNotMatch(source, /onclick/i); + assert.doesNotMatch(source, /alert\(1\)/); + assert.ok(removed.includes('onclick attribute')); + assert.deepEqual(findActiveContent(source), []); + }); + + test(`detects and removes ${article} ${label} script URI`, () => { + const svg = ``; + assert.deepEqual(findActiveContent(svg), [ + 'contains a script URI in href="javascript:..."', + ]); + + const { source, removed } = stripActiveContent(svg); + assert.doesNotMatch(source, /javascript:/i); + assert.ok(removed.includes('href attribute (javascript:)')); + assert.deepEqual(findActiveContent(source), []); + }); + + test(`detects and removes ${article} ${label} embedded document attribute`, () => { + const svg = ``; + assert.deepEqual(findActiveContent(svg), [ + 'contains an embedded document attribute: srcdoc (carries markup that executes in this origin)', + ]); + + const { source, removed } = stripActiveContent(svg); + assert.doesNotMatch(source, /srcdoc/i); + assert.ok(removed.includes('srcdoc attribute (embedded document)')); + assert.deepEqual(findActiveContent(source), []); + }); + + test(`reads ${article} ${label} value without disturbing inert markup`, () => { + const svg = ``; + assert.deepEqual(findActiveContent(svg), []); + assert.deepEqual(stripActiveContent(svg), { source: svg, removed: [] }); + }); +}