Repository navigation
[scanner] findRemoteReferences misses CSS comment- and escape-obfuscated url()/@import targets in style blocks #1091
Description
Finding
findRemoteReferences() in scripts/lib/svg-active-content.mjs — the gate that keeps a visitor's IP, User-Agent and Referer from reaching a host an SVG's author chose (per the validator's own threat note in scripts/validate-architecture-assets.mjs:262-269) — scans SVG style-block CSS raw. Attribute values get entity-decoding and whitespace stripping (normalizeUri, :458), but CSS text passed to cssTargets() (:405) gets neither comment removal nor escape decoding, and CSS_URL_PATTERN/CSS_IMPORT_PATTERN only match the unobfuscated forms.
Per CSS Syntax Level 3, comments are removed before tokenization and escapes in url tokens are decoded, so a browser fetches all of these — confirmed live against main @ 8afaa67:
Steps to Reproduce / Evidence
node --input-type=module -e "
import { findRemoteReferences } from './scripts/lib/svg-active-content.mjs';
findRemoteReferences('<svg><style>.a{background:url( /**/ \"https://evil.example/x\" )}</style></svg>') // => [] MISSED
findRemoteReferences('<svg><style>@import /**/ \"https://evil.example/x\";</style></svg>') // => [] MISSED
findRemoteReferences('<svg><style>.a{background:url(\\68 ttps://evil.example/x)}</style></svg>') // => [] MISSED (\\68 = 'h')
"
The unobfuscated baseline url("https://evil.example/x") is detected, so this is a gap in normalization, not in the reporting path.
This is the residual bypass class of the recently landed hardening series (#1039, #1044, #1056, #1063, #1068): each closed one parser-divergence between the scanner and a real CSS/XML parser; comment and escape handling inside CSS values is the divergence still open.
Recommendation
Normalize CSS before pattern matching in cssTargets(): strip /* ... */ comments (careful: not inside strings), and decode CSS escapes (\\XX hex plus the simple \\<char> form) in url tokens before testing with remoteTarget(). Add regression tests for the three cases above. One deliverable — a single PR against scripts/lib/svg-active-content.mjs plus tests/svg-active-content.test.mjs closes it.
Filed by scanner agent (ACMM L4 — issues-only mode)
🐝 Hive Agent: scanner | Instance: hosted-available-lke648397-260827-5n31 | SHA: unknown
— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
Activity
hivecommons-hive commentedon Oct 6, 2026
Scanner re-verification (2026-10-06, main @ 7ab301e) — confirmed, plus two additional evasion forms
Re-ran the original three cases live against
scripts/lib/svg-active-content.mjs— all still missed, control still caught:url( /**/ "https://evil.example/x.png" )@import /**/ "https://evil.example/y.css"url("\68 ttps://evil.example/z.png")url("https://evil.example/ok.png")(control)Two more confirmed evasion forms in the same
cssTargets/remoteTargetpath, both consequences of the same missing CSS-escape decode:url("\000068ttps://evil.example/a.png")— MISSED. CSS allows both the short\68and the explicit six-digit\000068spellings; a fix that only handles the short form (e.g. a single regex) leaves this open.//separators:url("https:\00002F\00002Fevil.example/c.png")— MISSED. Decoding escapes only before scheme matching is not enough;remoteTarget's backslash/separator normalization must run on the decoded value, because the authority separator itself can be escape-coded.For completeness, these variants ARE caught today and a fix must not regress them: tab-padded
url(\thttps://…\t),@import url("…")functional form,URL(uppercase, and a comment embedded in the host (https://ev/**/il.example/…, flagged verbatim).Repro:
node --input-type=module -e 'import {findRemoteReferences} from "./scripts/lib/svg-active-content.mjs"; …'with the strings above.Fix guidance unchanged (strip comments + full CSS-escape decode before pattern matching in
cssTargets, then run the existingremoteTargetnormalization on the decoded value) — just note the acceptance tests need to cover all five missed forms, not only the original three.scanner agent re-verification (ACMM L4 — issues-only mode)
🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:7ab301e— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
hivecommons-hive commentedon Oct 6, 2026
Re-verified at 7ab301e (2026-10-06): all three evasions still pass live —
url( /**/ "https://evil.example/x" ),@import /**/ "…", and\\68 ttps://escape-coded schemes each return[]from findRemoteReferences while the plain form is caught.Impact has widened since filing:
mirrorArtworkUrls()in scripts/lib/architecture-content.mjs now gates mirrored-SVG writes onfindRemoteReferences(source)returning empty (added after 900592b). A cncf/artwork SVG using the CSS-comment spelling sails through that new write-gate and validate-architecture-assets, landing in static/ where it beacons visitor IP/UA/Referer to the remote host — the exact hot-link the mirror exists to prevent. This makes the cssTargets comment-strip/escape-decode gap a publish path, not just a validator gap.scanner agent (ACMM L4 — issues-only mode)
🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:unknown— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
mrbobbytables commentedon Oct 7, 2026
Scope note: PR #1090 (for #1089) decodes CSS escapes before the remote-reference gate, which addresses the escape-obfuscation vector here. This issue stays open for the residue: comment-obfuscated url()/@import targets (e.g. url(ht/**/tps://...)), which #1090 does not strip.
hivecommons-hive commentedon Oct 7, 2026
Re-verified at main 8e3516f (2026-10-07). Partially fixed since the last check at 7ab301e:
cssTargets(scripts/lib/svg-active-content.mjs:477) pipes values throughdecodeCssEscapes(:308), andCSS_URL_PATTERN(:348) carries an escape-aware bare branch. Live test:url("\\68 ttps://evil.example/x.css")in a<style>block is reported.CSS_URL_PATTERNallows only\s*afterurl(andCSS_IMPORT_PATTERNonly\s+after@import, so a comment between tokens defeats both. Live results at HEAD:url(/**/ "https://evil.example/x.css")→ no finding@import /**/ "https://evil.example/x.css";→ no findingurl("https://…")→ reported (control)CSS strips comments before tokenization, so a pre-pass removing
/*…*/(or allowing comment spans in the patterns' whitespace positions) closes the residual gap.🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:8e3516f— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
hivecommons-hive commentedon Oct 7, 2026
Re-verified at main 03cfcfe (2026-10-07, post-#1159). Status unchanged since the 8e3516f check:
url(/**/ "https://evil.example/x.css")→ no finding@import /**/ "https://evil.example/y.css";→ no finding\\68, six-digit\\000068, escape-coded\\00002F\\00002Fseparators, plus comment-in-host and plain controls.The residual fix is still a comment-strip pre-pass (or comment spans allowed in the patterns' whitespace positions) in
cssTargetsbefore pattern matching.scanner agent re-verification (ACMM L4 — issues-only mode)
🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:03cfcfe🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:unknown— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
hivecommons-hive commentedon Oct 8, 2026
test comment routing
🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:e13f506— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88
hivecommons-hive commentedon Oct 8, 2026
Verified resolved at main e13f506 (fix merged in #1176). Live-checked the three reported vectors against scripts/lib/svg-active-content.mjs:
url(/**/"https://…"),@import /**/ "…", and escape-coded\\68 ttps://are all now detected in <style> blocks — cssTargets strips CSS comments and decodes escapes before matching. tests/svg-active-content.test.mjs passes (157/157 across the four security test files). Issue closed. (Supersedes the accidental 'test comment routing' comment above, which I cannot delete with scanner-tier credentials.)— scanner agent (verify-once)
🐝 Hive Agent:
scanner| Instance:hosted-available-lke648397-260827-5n31| SHA:e13f506— hive: agent=scanner backend=copilot model=kimi-k3 copilot=1.0.88