Skip to content

fix(security): honour CSS escapes when pairing quotes in the SVG scanner - #1206

Closed
hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
sec/css-escape-quote-pairing
Closed

hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
sec/css-escape-quote-pairing

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

scripts/lib/svg-active-content.mjs paired CSS quote characters without honouring escape sequences, in two places. Both desynchronised the scan and hid a remote resource reference from findRemoteReferences() — the gate mirrorArtworkUrls(), mirrorLandscapeLogo() and scripts/validate-architecture-assets.mjs rely on to keep a third-party host out of an SVG published from static/ at the site origin.

stripCssComments() had no backslash handling, so font-family: \" — an escaped quote character, which does not open a string — was read as a string opener and everything to the next quote in the fragment was copied verbatim, leaving the CSS comments in that span unstripped. A comment between a later url( and its quoted argument then survived, and CSS_URL_PATTERN matches neither the commented url( nor the text after it. The same blindness let an unterminated string run past the newline that ends it as a bad-string.

CSS_STRING split "a\"" into two strings rather than one, shifting every later quote by one and leaving a bare-string image-set() target inside what the scan then read as unquoted filler.

Both now go through one endOfCssString() helper that skips an escaped code point and ends a string at a newline, matching the tokenizer. cssStringAt() returns the raw contents on the same rule, replacing the CSS_STRING regex.

Verification

  • Three regression tests added to tests/svg-active-content.test.mjs; all three fail on main and pass here.
  • node --test tests/svg-active-content.test.mjs: 124 pass, 0 fail (121 before).
  • V8 coverage over that run reports exactly one uncovered region in the file, the pre-existing ?? '' fallback at line 251; every line added here is exercised.
  • npx prettier --check clean on both files.
  • css-tree parses the escaped-quote case as two separate rules and the image-set() case as two strings (a", https://evil.example/shifted.png), confirming the escape changes what this scanner sees and not what the stylesheet means.

Files touched: scripts/lib/svg-active-content.mjs (stripCssComments, imageSetArguments, new endOfCssString/cssStringAt, CSS_STRING removed) and tests/svg-active-content.test.mjs. No overlap with #1203.

Closes #1205

This is an interim fix on the per-bypass path #1194 describes; it does not substitute for the parser-based direction decided there.


Filed by sec-check agent (ACMM L4/L5 — hold-gated mode). Hold-gated: human review required.

— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88 requested_by=@mrbobbytables

stripCssComments() and the image-set() argument scan in
scripts/lib/svg-active-content.mjs paired CSS quote characters without
honouring escape sequences, so a backslash-escaped quote desynchronised
both scans and hid a remote resource reference from
findRemoteReferences().

" is an escaped quote character and does not open a string, but
stripCssComments() read it as one and copied to the next quote in the
fragment verbatim, leaving any CSS comment in between unstripped. A
comment between a later url( and its quoted argument then survived, and
CSS_URL_PATTERN matches neither the commented url( nor the text after
it. The same blindness let an unterminated string run past the newline
that ends it as a bad-string, blinding the strip for the rest of the
fragment.

CSS_STRING split "a\"" into two strings rather than one, shifting
every later quote by one and leaving a bare-string image-set() target
inside what the scan then read as unquoted filler.

Both now go through one endOfCssString() helper that skips an escaped
code point and ends a string at a newline, matching the tokenizer.

Closes #1205

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
@hivecommons-hive hivecommons-hive Bot added the hold label Oct 8, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "sec-check" agent while Hive policy required a human checkpoint for that agent. Non-outreach agents are held at ACMM L3–L5; the outreach agent is always held because it publishes project-facing communication.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@mrbobbytables

Copy link
Copy Markdown
Member

Superseded by #1245, which replaces the regex scanners with parser-based ones (the approach decided on #1194) and covers this fix, with regression tests. Closing in favour of that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[sec-check] CSS escape sequences desynchronise quote pairing in svg-active-content.mjs, hiding remote references from findRemoteReferences()

1 participant