Skip to content

fix(security): gate static/ root files through the published-asset security gate - #827

Merged
mrbobbytables merged 1 commit into
mainfrom
sec/gate-static-root-files
Sep 29, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
sec/gate-static-root-files

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

static/ is copied verbatim into the build and served at the site origin, and scripts/validate-architecture-assets.mjs gates everything published there — extension allow-list, symlink rejection, SVG active-content scanning.

checkForUngatedStaticDirs() skipped every non-directory entry on its first statement, so a regular file placed directly in static/ had no enclosing directory for assetDirs to gate and was never extension-checked.

Verified on 7772dcf: static/pwn.html containing <html><script>alert(document.domain)</script></html> passed validate:architecture-assets (exit 0), the full unit suite (exit 0, 1421 pass / 0 fail) and build:production, shipping verbatim as build/pwn.html — served from the site origin with no CI signal from any gate.

tests/static-svg-active-content.test.mjs already catches a script-bearing .svg at the root, but it only inspects .svg files and so does not constrain the file type. The gap closed here is the missing extension allow-list on top-level files.

What this changes

scripts/validate-architecture-assets.mjs

  • checkForUngatedStaticDirs → checkForUngatedStaticEntries, which now handles top-level files as well as directories. Directory handling is unchanged.
  • New exemptTopLevelFiles map names the three legitimate non-asset root files (.nojekyll, manifest.json, robots.txt), each with the reason a reviewer needs to approve a new exemption — mirroring how exemptTopLevelDirs documents fonts.
  • Every other top-level file goes through validateAsset(..., SITE_CHROME_EXTENSIONS), which rejects anything the browser executes as markup and routes an SVG through the active-content scan.
  • Corrects the comment above assetDirs that documented this gap as accepted.

tests/validate-architecture-assets.test.mjs — four tests appended: a top-level .html is rejected, a top-level script-bearing .svg is rejected, the three exempted root files are accepted, and a legitimate top-level image is accepted.

Verification

  • Clean tree: validate:architecture-assets exit 0 (unchanged, same single pre-existing foreignObject warning)
  • Both PoCs now rejected, exit 1: .html is not an allowed asset type; static/ is served at the site origin and active content: contains a <script> element
  • test:unit:coverage:check exit 0
  • check:format exit 0
  • Mutation-checked both directions: with the fix reverted, exactly the two new security tests fail (47 pass / 2 fail); the two "accepts" tests pass either way, confirming they assert no regression rather than the fix itself.

Closes #826


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

…curity gate

static/ is copied verbatim into the build and served at the site origin, and
validate-architecture-assets.mjs gates everything published there. But
checkForUngatedStaticDirs() skipped every non-directory entry, so a regular
file placed directly in static/ had no enclosing directory for assetDirs to
gate and was never extension-checked: static/pwn.html passed the validator,
the full unit suite and the production build, shipping to the site origin with
no CI signal.

Gate top-level files individually, mirroring the existing directory treatment:
the known non-asset files are named in exemptTopLevelFiles and everything else
is held to SITE_CHROME_EXTENSIONS, which rejects anything the browser executes
as markup and routes an SVG through the active-content scan.

Signed-off-by: sec-check <sec-check@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.

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

Labels

agent/security Approved by a Hive merger/owner for auto-merge on green CI hive/hosted-available-lke648397-260827-5n31 Approved by a Hive merger/owner for auto-merge on green CI 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] static/ root files bypass the published-asset security gate: a top-level .html ships to the site origin with no CI signal

1 participant