Skip to content

fix(security): gate SVGs in static/img and static/favicons at the site origin - #691

Closed
hivecommons-hive[bot] wants to merge 2 commits into
mainfrom
sec/static-svg-gate
Closed

hivecommons-hive[bot] wants to merge 2 commits into
mainfrom
sec/static-svg-gate

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

scripts/validate-architecture-assets.mjs is the gate that keeps active content out of the bytes the site publishes at its own origin. Its header comment states the rule it enforces — "Every directory here is published verbatim at the site origin, so every one gets the security gate" — but assetDirs listed only three of static/'s image roots:

  • static/img/architectures
  • static/img/cncf-projects
  • static/img/awards

static/img itself and static/favicons were never walked, so five SVGs reached the site origin with no active-content check and no extension allow-list:

  • static/img/cloud-native-end-users.svg
  • static/img/cloud-native-end-users-dark.svg
  • static/img/cncf_logo_white.svg (referenced from src/theme/Footer/index.js)
  • static/img/favicon.svg
  • static/favicons/favicon.svg

SVG is a document format, not merely an image format: a browser that navigates directly to /img/<name>.svg parses it as XML and executes any <script>, on* handler or javascript: URI it carries, in the site's own origin. A pull request refreshing a logo or favicon therefore had no automated gate, while the identical file one directory deeper in static/img/awards/ was rejected.

Verified against main before the change: a <script>-bearing SVG in static/img, a javascript: URI SVG in static/favicons, and a .html file in static/img all pass npm run validate:architecture-assets with exit 0. With this change all three are errors and the run exits 1.

What this changes

Files touched: scripts/validate-architecture-assets.mjs, tests/validate-architecture-assets.test.mjs.

  • Adds static/img (shallow) and static/favicons to assetDirs.
  • walk() takes a recurse flag. static/img is walked shallowly because its image subdirectories are already listed as their own roots, each with its own quality setting. The symlink check runs before the directory branch, so a symlinked directory is still reported in a shallow walk.
  • validateAsset() takes a per-root extension set. The two chrome roots use ALLOWED_ASSET_EXTENSIONS plus .ico, since static/img serves the legacy favicon.ico. .ico is kept out of ALLOWED_ASSET_EXTENSIONS itself because that set mirrors MIRRORABLE_ASSET_EXTENSIONS in scripts/import-architectures.mjs and the importer never writes an .ico; ICO is a raster container no browser parses as markup or script.
  • static/fonts and the static/ root (robots.txt, manifest.json, .nojekyll) stay outside the gate: they hold no SVG and their extensions are legitimately outside the image allow-list.

No asset bytes change. The five previously ungated SVGs already declare xmlns, carry no DOCTYPE and carry no active content, so they pass unchanged — this is a gate-only change. Gated coverage goes from 69 to 82 published assets.

Verification

  • node --test tests/validate-architecture-assets.test.mjs — 40 pass, 0 fail (6 new tests)
  • node --test tests/static-assets.test.mjs tests/validate-utils.test.mjs — 15 pass, 0 fail
  • node scripts/validate-architecture-assets.mjs — exit 0, "Validated 82 architecture asset(s)"
  • prettier@3.9.8 --check on both changed files — clean

New tests cover: active content in a static/img chrome SVG, active content in a static/favicons SVG, a non-image extension in static/img, favicon.ico acceptance, the shallow-walk scoping of diagram-quality checks, and a symlinked directory sitting directly in static/img.

Closes #690


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

…e origin

scripts/validate-architecture-assets.mjs states that it gates every
directory published verbatim at the site origin, but assetDirs listed only
static/img/architectures, static/img/cncf-projects and static/img/awards.
static/img itself and static/favicons were never walked, so the five SVGs
served from them - including the footer logo and both favicons - reached
the origin with no active-content check and no extension allow-list.

SVG is a document format: a browser that navigates directly to one parses
it as XML and executes any script it carries, in the site's own origin. A
pull request refreshing a logo or favicon therefore had no automated gate,
while the identical file one directory deeper was rejected.

Add both roots. static/img is walked shallowly because its image
subdirectories are already listed with their own quality settings, so
walk() takes a recurse flag; the symlink check runs before the directory
branch so a symlinked directory is still reported in a shallow walk.
static/img also serves the legacy favicon.ico, so the two chrome roots use
an extension set of ALLOWED_ASSET_EXTENSIONS plus .ico - kept separate
because ALLOWED_ASSET_EXTENSIONS mirrors what the importer mirrors, and
the importer never writes an .ico. ICO is a raster container no browser
parses as markup.

static/fonts and the static/ root stay outside the gate: no SVG, and
extensions legitimately outside the image allow-list.

The five previously ungated SVGs already pass unchanged; this is a
gate-only change. Coverage goes from 69 to 82 published assets.

Signed-off-by: hivecommons-hive[bot] <hivecommons-hive@hive.kubestellar.io>
@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

Copy link
Copy Markdown
Contributor Author

CI note (ci-maintainer): the red Validate repository on the current head is PR-local, not baseline — every other open PR branch ran the same check green in the last 24h, and main is green.

Failing test: tests/validate-architecture-assets-symlink-root.test.mjs → a regular file at an asset root is skipped, not walked (run 36227025758).

Cause: this PR's new extensionless-file gate now fires on the fixture the test plants at static/img/architectures:

1 error(s) in architecture assets:
  [error] static/img/architectures: extensionless file is not an allowed asset type; static/ is served at the site origin
1 !== 0

The test expects a regular file at the asset root to be skipped (0 errors); the new gate reports 1. Either the gate should skip non-directory asset roots before the extension check, or the test fixture/expectation needs updating to match the new intended behavior.

🐝 Hive Agent: ci-maintainer | Instance: hosted-available-lke648397-260827-5n31 | SHA: unknown

— hive: agent=ci-maintainer backend=copilot model=kimi-k3 copilot=1.0.88

The new shallow walk of static/img enumerated static/img/architectures,
static/img/cncf-projects and static/img/awards as ordinary entries. Each
of those is already an asset root with its own entry in assetDirs, which
decides for itself whether a symlink is an error and whether a
non-directory root is skipped.

Re-entering them from the parent walk duplicated the symlink error and,
where a root was a regular file rather than a directory, rejected it as
an extensionless asset - breaking the pre-existing
'a regular file at an asset root is skipped, not walked' test.

Skip any entry whose path is itself a declared asset root.

Signed-off-by: hivecommons-hive[bot] <hivecommons-hive@hive.kubestellar.io>
@mrbobbytables

Copy link
Copy Markdown
Member

Superseded by #753, which consolidates the six open security-fix PRs (commits cherry-picked unmodified, authorship and DCO preserved).

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[sec-check] SVGs in static/img and static/favicons are published at the site origin without the active-content gate

1 participant