Skip to content

[sec-check] findRemoteReferences misses image-set() bare-string URLs, a spec-defined remote fetch with no url() token #1175

Closed

Description

@hivecommons-hive

Security Finding

Severity: medium
Type: unsafe-pattern (scanner bypass)
Component: cssTargets() / CSS token patterns in scripts/lib/svg-active-content.mjs

findRemoteReferences() is the gate that keeps a visitor's IP, User-Agent and
Referer from reaching a host an imported SVG's author chose. It finds a CSS
remote target only through CSS_URL_PATTERN (a url(...) token) or
CSS_IMPORT_PATTERN (an @import bare string).

CSS Images Level 4 defines a third spelling that is neither. Its grammar gives
an image-set option as "an image or a string", followed by an optional
resolution and type(), and the spec states plainly:

Each string inside image-set() represents a URL.

(https://drafts.csswg.org/css-images-4/#image-set-notation)

So image-set("https://evil.example/x.png" 1x) is a live remote fetch that
carries no url( token for CSS_URL_PATTERN to find and no @import for
CSS_IMPORT_PATTERN — the same shape of gap CSS_IMPORT_PATTERN exists to
close for @import. image-set() is accepted wherever a CSS image value is
(cursor, background-image, mask-image, list-style-image), and the
long-supported -webkit-image-set() alias has the same effect.

Steps to Reproduce / Evidence

Verified locally against main @ 03cfcfe, node v26.10.0:

$ node --input-type=module -e "
import { findRemoteReferences as f } from './scripts/lib/svg-active-content.mjs';
const S = s => '<svg><style>.a{cursor:' + s + '}</style></svg>';
console.log('bare string:', f(S('image-set(\"https://evil.example/x.png\" 1x)')));
console.log('url() form :', f(S('image-set(url(\"https://evil.example/x.png\") 1x)')));
"
bare string: []                                                        <-- MISSED
url() form : [ 'references a remote resource in a style block: https://evil.example/x.png' ]

The url() spelling of the identical fetch is detected, so this is a missing
token pattern, not a gap in the reporting path.

Impact

An SVG imported unattended from cncf/architecture — or mirrored by
mirrorArtworkUrls(), which calls findRemoteReferences() as a pre-write gate
at scripts/lib/architecture-content.mjs:184 — can ship a stylesheet that
beacons every visitor who opens the published diagram to a host the diagram's
author chose, disclosing their IP address, User-Agent and Referer. That is the
precise harm this gate exists to prevent, and validate-architecture-assets.mjs
would pass the file.

The site CSP's img-src allowlist blunts the background-image spelling, but
cursor and mask-image are not img-src fetch directives and
docusaurus.config.js deliberately sets no default-src, so those spellings
are unrestricted.

Relationship to #1091 — disjoint

#1091 covers a normalization gap in the same function: CSS comments and CSS
escapes. This is a missing construct, and no amount of comment-stripping or
escape-decoding reaches it — image-set() has to be given a pattern.

While confirming that, #1091's reproducers were re-checked at 03cfcfe. Its
escape half has since landed (decodeCssEscapes, CSS_URL_PATTERN):
url(\68 ttps://evil.example/x) is now correctly flagged. Its comment half is
still open — url( /*c*/ "https://...") and @import /*c*/ "https://..." are
both still missed.

Because a fix here and the remaining half of #1091 edit the same pattern
constants in the same file, they should land as one PR rather than two over the
same lines.

Recommendation

In scripts/lib/svg-active-content.mjs:

  1. Collect the bare-string arguments of image-set() / -webkit-image-set()
    and run them through decodeCssEscapes() and remoteTarget() in
    cssTargets() the way CSS_IMPORT_PATTERN already is. A target matched by
    more than one pattern is reported once, because findRemoteReferences()
    collects into a Set. The argument list needs a parenthesis-depth walk
    rather than a regex, since url(...) and type(...) nest inside it.
  2. Strip CSS comments before pattern matching, taking care not to strip inside
    a CSS string, closing the remaining half of [scanner] findRemoteReferences misses CSS comment- and escape-obfuscated url()/@import targets in style blocks #1091.

Completion criteria

  • image-set("https://host/x.png" 1x) in a style block is reported
  • -webkit-image-set("https://host/x.png" 1x) is reported
  • An escape inside an image-set() string does not hide the host
  • url( /*c*/ "https://host/x") is reported
  • @import /*c*/ "https://host/x" is reported
  • A comment opener inside a CSS string is not treated as a comment
  • Regression tests in tests/svg-active-content.test.mjs for each case

Filed by sec-check agent (ACMM L4/L5 — hold-gated mode)


🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: 03cfcfe

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

Activity

  1. added
    securityApproved by a Hive merger/owner for auto-merge on green CI
    agent/sec-checkCreated by Hive for agent-filed issue provenance
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    on Oct 8, 2026
  2. added
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    and removed
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    on Oct 8, 2026
  3. hivecommons-hive commented on Oct 8, 2026

    @hivecommons-hive
    ContributorAuthor

    Verified resolved at main e13f506 (fix merged in #1176). Live check: image-set("https://evil.example/x.png" 1x) in a <style> block is now reported by findRemoteReferences. Test suite passes.

    — 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

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/likely-doneHive verified that a merged PR references or claims this issue; pending confirmationsecurityApproved 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