Skip to content

fix: validate architecture catalog sourceUrl scheme and asset containment (scripts/validate-architectures.mjs) - #334

Merged
mrbobbytables merged 1 commit into
mainfrom
sec/validate-architectures-url-containment
Sep 21, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
sec/validate-architectures-url-containment

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

scripts/validate-architectures.mjs is the only gate between
data/architectures/catalog.json — regenerated nightly from the third-party
cncf/architecture repository — and the published site. It checked neither of
the two fields that reach a render sink.

sourceUrl was not validated at all. scripts/generate-members.mjs:145-146
copies it into members.json sourceAttribution, which
src/components/MemberDirectory/index.js:196 renders as an href with no
scheme guard. A javascript: value passed npm run validate:architectures and
reached the published site.

assets[] containment was never asserted. The existence probe built its
path with join(root, 'static', asset.replace(/^\//, '')), which normalises
.. away, so the probe escaped static/ entirely.

Changes (all in scripts/validate-architectures.mjs)

  • isHttpsUrl()sourceUrl must be present and parse as an https: URL via
    new URL(), not a prefix or substring test.
  • resolveContainedAsset() — every assets[] entry must start with
    /img/architectures/ and, after resolution, still be contained in that
    directory. Resolve first, then assert the prefix, so .. cannot normalise the
    guard away. existsSync runs only on a contained path. This is the same
    resolve-then-contain ordering [sec-check] validate-awards.mjs logo containment check is defeated by '..' — path escapes static/img/awards/ and existsSync probes the build runner #327 records for validate-awards.mjs.
  • id must match /^[a-z0-9][a-z0-9-]*$/, since it is used both as a route
    segment (/architectures/<id>) and as a filesystem path component in
    import-architectures.mjs.

Verification

Clean catalog still validates:

$ node scripts/validate-architectures.mjs
Validated 7 architecture records   # exit 0

A catalog carrying a javascript: sourceUrl, an escaping asset, an asset with
an interior .., and a ../evil id is now rejected:

4 error(s) in architecture catalog:
  [error] adobe: sourceUrl must be an https URL; it is rendered as an href in the member directory
  [error] adobe: asset /../../../../etc/hostname must be a site-absolute path contained in /img/architectures/
  [error] allianz: asset /img/architectures/../../../etc/hostname must be a site-absolute path contained in /img/architectures/
  [error] ../evil: id must be a lowercase slug matching /^[a-z0-9][a-z0-9-]*$/; ...
# exit 1

npx prettier --check scripts/validate-architectures.mjs fails identically on
main and on this branch — the file is part of the pre-existing 62-file
formatting baseline tracked in #305, so it is deliberately not reformatted here.

Scope

Touches exactly one file, scripts/validate-architectures.mjs, and only the
sourceUrl / assets[] / id validation cluster. Disjoint from the open work
on import-architectures.mjs (#195, #212, #216, #249, #266),
validate-metrics.mjs (#304, #263), validate-awards.mjs (#329),
validate-architecture-assets.mjs (#231) and generate-members.mjs (#208).

Closes #332


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

…sset containment

scripts/validate-architectures.mjs is the only gate between
data/architectures/catalog.json -- regenerated nightly from the third-party
cncf/architecture repository -- and the published site, but it checked neither
of the two fields that reach a render sink.

- sourceUrl was not validated at all. generate-members.mjs copies it into
  members.json sourceAttribution, which MemberDirectory renders as an href
  with no scheme guard, so a javascript: value passed validation and reached
  the published site.
- assets[] containment was never asserted, and the existence probe built its
  path with join(root, 'static', asset.replace(/^\//, '')), which normalises
  '..' away -- so the probe escaped static/ entirely.

sourceUrl must now parse as an https: URL via new URL(). Assets must start with
/img/architectures/ and, after resolution, still be contained in that directory
-- resolve first, then assert the prefix, so '..' cannot normalise the guard
away. id must be a lowercase slug, since it is used both as a route segment and
as a filesystem path component.

Verified: the clean catalog still validates (7 records, exit 0); a catalog
carrying a javascript: sourceUrl, an escaping asset, an asset with interior
'..', and a '../evil' id is rejected with four errors and exit 1.

Signed-off-by: kubestellar-hive[bot] <kubestellar-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 hivecommons-hive Bot added security Approved by a Hive merger/owner for auto-merge on green CI 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 labels Sep 20, 2026
@mrbobbytables
mrbobbytables added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit dc5fe09 Sep 21, 2026
1 of 2 checks passed
@castrojo
castrojo deleted the sec/validate-architectures-url-containment branch September 21, 2026 15:14
mrbobbytables pushed a commit to mrbobbytables/endusers that referenced this pull request Sep 21, 2026
… asset rules (cncf#398)

PR cncf#334 added mandatory sourceUrl, id-slug and asset-containment rules to
scripts/validate-architectures.mjs without updating the test fixtures. The
validRecord fixture has no sourceUrl, so 'accepts a valid catalog' and
'accepts records without an assets array' both fail, leaving npm run
test:unit red on main and the ci.yml 'Validate repository' check red on
every open PR.

Add sourceUrl to the fixture and cover the three rules cncf#334 introduced:
sourceUrl absent / http: / javascript: / unparseable / non-string, a
non-slug id, and assets outside the prefix, escaping it via .., or not a
string.

Closes cncf#397

Signed-off-by: kubestellar-hive[bot] <kubestellar-hive@hive.kubestellar.io>
Co-authored-by: kubestellar-hive[bot] <kubestellar-hive@hive.kubestellar.io>
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 hold security Approved by a Hive merger/owner for auto-merge on green CI

Projects

None yet

1 participant