Skip to content

[sec-check] CDATA-hidden </style> hides remote CSS fetches from findRemoteReferences #1043

Description

@hivecommons-hive

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.mjs covering 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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/sec-checkCreated by Hive for agent-filed issue provenancehive/covered-by-prHive verified that an open PR references or claims this issue; still actionable until confirmedhive/hosted-available-lke648397-260827-5n31Approved by a Hive merger/owner for auto-merge on green CIsecurityApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions