Skip to content

fix(security): treat backslash authority prefixes as remote in remoteTarget() - #1068

Merged
mrbobbytables merged 1 commit into
mainfrom
sec/fix-backslash-authority
Oct 5, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
sec/fix-backslash-authority

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

remoteTarget() in scripts/lib/svg-active-content.mjs is the single host
test behind findRemoteReferences(). It accepted only https?:// and a //
prefix.

The WHATWG URL standard treats \ as equivalent to / in the scheme and
authority prefix of a special-scheme URL, and the site is served over
https, so every relative reference in a published SVG resolves against a
special-scheme base. These values therefore reach a third-party host exactly
as //host does, while the gate reported every one of them local:

value in the SVG resolves to before after
\\evil.example/p https://evil.example/p missed flagged
/\evil.example/p https://evil.example/p missed flagged
\/evil.example/p https://evil.example/p missed flagged
https:\\evil.example/p https://evil.example/p missed flagged
https:/\evil.example/p https://evil.example/p missed flagged
//evil.example/p https://evil.example/p flagged flagged

Because findRemoteReferences() routes every href/src value and every
url()/@import CSS target through remoteTarget(), the bypass applied to
all of them at once, and it is the only remote-reference check in
scripts/lib/enduser-collector.mjs:201 as well.

What changed

remoteTarget() now normalizes the leading authority prefix the way the URL
parser does, translating \ to / only in the leading run of separators.

The narrow scope is deliberate and is pinned by tests: a single leading
separator keeps the value on this origin (\host is the path /host, and
https:/host likewise), and an interior backslash stays an ordinary path
character, so a\b/c.png and ./sub\dir/x.png still report local. Widening
the translation would have failed diagrams that reference their own sibling
assets.

Verification

End to end through the real validator, with the PoC planted under
static/img/architectures/:

before:  Validated 173 architecture asset(s).          exit 0
after:   2 error(s) in architecture assets:            exit 1
           remote reference: ... <image> href: //evil.example/track.png
           remote reference: ... <use> xlink:href: //evil.example/sprite.svg#g
  • npm run test:unit — 1923/1923 pass (1912 baseline + 11 added here)
  • Reverting only the remoteTarget() change fails 7 of the 11 added tests, so
    they are load-bearing rather than restating current behaviour
  • npm run check:format clean
  • npm run test:unit:coverage:check passes (99.40% lines / 94.88% regions)

Scope

Touches remoteTarget() only (scripts/lib/svg-active-content.mjs:243) plus
its tests. Deliberately disjoint from the other open held PRs:

Closes #1067


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

remoteTarget() accepted only `https?://` and a `//` prefix when deciding
whether an SVG value points at another host. The URL parser treats `\` as
`/` in the scheme and authority prefix of a special-scheme URL, and the site
is served over https, so `\\host`, `/\host`, `\/host` and
`https:\\host` all resolve to host exactly as `//host` does while the
gate reported them local.

findRemoteReferences() routes every href/src value and every url()/@import
CSS target through remoteTarget(), so an imported diagram could reference an
attacker-chosen host and still pass validate:architecture-assets. A visitor
opening the published SVG then discloses their IP, User-Agent and Referer to
that host -- the exposure the gate exists to prevent.

Translate only the leading run of separators: a single separator keeps the
value on this origin (`\host` is the path /host) and an interior backslash
stays an ordinary path character, so `./sub\dir/x.png` is still local.

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
@hivecommons-hive hivecommons-hive Bot added the hold label Oct 4, 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 automatically remove the hold label once current policy no longer requires a level hold for "sec-check". If this is an outreach PR, a human must review it and remove the label.

@hivecommons-hive hivecommons-hive Bot added the security Approved by a Hive merger/owner for auto-merge on green CI label Oct 4, 2026
@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit c02198d Oct 5, 2026
7 checks passed
@mrbobbytables
mrbobbytables deleted the sec/fix-backslash-authority branch October 7, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold security Approved by a Hive merger/owner for auto-merge on green CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[sec-check] remoteTarget() misses backslash authority prefixes, so an SVG can reference a remote host past findRemoteReferences()

1 participant