[sec-check] CDATA-hidden </style> hides remote CSS fetches from findRemoteReferences
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 30/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- javascript
- Domain
- security
Research direction
Start in scripts/lib/svg-active-content.mjs at STYLE_BLOCK_PATTERN and findRemoteReferences, then inspect tests/svg-active-content.test.mjs. Run the SVG active-content tests and add coverage for the CDATA decoy, a second block after it, an unclosed style block, and self-closing style; done means these cases are scanned conservatively without changing self-closing behavior.
Written by the indexing model from the issue text.
Description
Security Finding
Severity: medium
Type: unsafe-pattern / security-control bypass
Component: scripts/lib/svg-active-content.mjs — STYLE_BLOCK_PATTERN / findRemoteReferences
findRemoteReferences() is an error-level gate in
scripts/validate-architecture-assets.mjs:268-270: every SVG published from
static/ is refused if it fetches a resource from a third-party host. The
<style> arm of that gate reads its CSS with a single lazy regex:
const STYLE_BLOCK_PATTERN = new RegExp(
`<\\s*${NS_PREFIX}style\\b(?:"[^"]*"|'[^']*'|[^>"'])*>([\\s\\S]*?)<\\s*/\\s*${NS_PREFIX}style\\s*>`,
'gi',
);
A browser parses a standalone .svg as XML, where a </style> inside a
CDATA section is ordinary character data and does not close the element. The
lazy match stops there anyway, so every declaration after the decoy is never
scanned — in a file that is well-formed and renders normally.
Proof of concept
This file is well-formed XML (verified with xml.etree.ElementTree) and its
url(...) is live CSS that a browser fetches when the SVG is opened at the
site origin:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<style type="text/css"><![CDATA[
/* decoy that closes the regex early: </style> */
rect { fill: url(https://evil.example/track.svg#g); }
]]></style>
<rect width="100" height="100"/>
</svg>
At main 900592b:
findRemoteReferences(payload) -> []
findActiveContent(payload) -> []
Both gates pass, so npm run validate:architecture-assets accepts the file and
it publishes.
A second, smaller hole in the same function: a <style> element that is never
closed matches nothing, so its CSS is not scanned at all. An HTML parser runs
such an element to EOF, and in XML the file is rejected outright, so reading
the remainder is the conservative answer under either grammar.
Impact
Defeats the control that exists specifically to stop a diagram from beaconing
visitors. A url() in a CSS rule discloses the visitor's IP address,
User-Agent and Referer to a host the diagram's author chose, from the site's
own origin. @font-face { src: url(...) } and @import have the same reach.
This is tracking/disclosure, not code execution — the element allow-list and
the <script> scan are unaffected — which is why this is filed as medium and
not high.
Reachable through either SVG path into static/: an architecture diagram
imported from cncf/architecture (scripts/import-architectures.mjs) and a
logo hand-committed in a pull request are both published verbatim at the origin
and both rely on this gate.
Recommendation
Replace STYLE_BLOCK_PATTERN with a scan that consumes CDATA sections whole
before < is given any meaning, and that reads an unclosed <style> to the
end of the document. A self-closing <style/> must still swallow no content.
-
<style>CSS is read by a CDATA-aware scan instead of one lazy regex - regression tests in
tests/svg-active-content.test.mjscovering the
CDATA decoy, a second block after a decoy, an unclosed block, and
<style/>
Cluster: scripts/lib/svg-active-content.mjs STYLE_BLOCK_PATTERN /
findRemoteReferences style-block scan. Disjoint from #1042, which only adds
tests for XML_DECLARATION_ENCODING and PROCESSING_INSTRUCTION, and from
#1039, which is scripts/lib/mdx-active-content.mjs.
Filed by sec-check agent (ACMM L4/L5 — hold-gated mode)
🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: 900592b
— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 2
- Avg merge
- 8h 22m
- Merged PRs (30d)
- 600
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
[quality] initials() drops a letter when a name carries leading whitespacePossibly taken @hivecommons-hive claimed this today. Openagent/quality hive/claimed-by-scanner stage/040-covered-by-pr testing
Difficulty 1/5 1-3 hours Newbie friendliness 86/100
cncf/endusers#1639 · 2 comments ·
Maintainers usually reply within 1 day
-
[guide] SECURITY.md Scope omits composite actions from the CodeQL scan descriptionPossibly taken @hivecommons-hive claimed this today. Openagent/guide documentation hive/claimed-by-guide stage/040-covered-by-pr
Difficulty 1/5 Under an hour Newbie friendliness 25/100
cncf/endusers#1637 · 1 comment ·
Maintainers usually reply within 1 day
-
[quality] Nothing guards the camelCase JSX DOM prop convention #1603 established in the footerPossibly taken @hivecommons-hive claimed this today. Openagent/quality hive/claimed-by-quality stage/040-covered-by-pr testing
Difficulty 2/5 1-3 hours Newbie friendliness 20/100
cncf/endusers#1633 · 1 comment ·
Maintainers usually reply within 1 day
-
[scanner] a11y: member directory card button accessible name does not contain its visible text (WCAG 2.5.3)Possibly taken @hivecommons-hive claimed this today. Openagent/scanner bug hive/claimed-by-scanner stage/040-covered-by-pr
Difficulty 2/5 1-3 hours Newbie friendliness 35/100
cncf/endusers#1630 · 1 comment ·
Maintainers usually reply within 1 day
-
[quality] test the e2e reporter's unexecuted-chunk arm, its only untested regionPossibly taken @hivecommons-hive claimed this 1 day ago. Openagent/quality hive/claimed-by-quality stage/040-covered-by-pr testing
Difficulty 2/5 1-3 hours Newbie friendliness 35/100
cncf/endusers#1625 · 5 comments ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
HarperFast/harper#3299 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
osmlab/name-suggestion-index#12992 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
open-government-design-system/ogds-elements#199 ·
Maintainers usually reply within 1 day
-
area:plugin type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Document annotations cannot anchor visible code after a shorter or different fenced-code markerOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
paperclipai/paperclip#16898 ·
Maintainers usually reply within 1 day