Skip to content

fix(awards): resolve award link targets through the URL parser - #508

Merged
mrbobbytables merged 1 commit into
mainfrom
sec/awards-url-parse
Sep 23, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
sec/awards-url-parse

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

scripts/validate-awards.mjs gated verifiedAgainst, announcementUrl,
caseStudyUrl and talkUrl with /^https:\/\//, a string prefix test that
only asserts a value begins with https://. It never established that the
value parsed as a URL and never looked at the authority, so it accepted:

value old gate real hostname
https://cncf.io@evil.example/phish passed evil.example (userinfo cncf.io)
https://user:pass@evil.example/ passed evil.example
https:// passed unparseable
https:// evil.example passed unparseable

All four fields are rendered directly as <a href> by
src/components/AwardsTimeline/index.js (lines 23, 46, 51, 56, 87) and
src/components/MemberDirectory/index.js (lines 196, 205, 214), so this
validator was the only automated gate between data/awards.json and a
clickable outbound link on the published site.

What changed

Both prefix tests are replaced with a single checkHttpsUrl() helper in
scripts/validate-awards.mjs that parses with new URL(), requires the
https: protocol, and rejects a userinfo component. This is not a new
convention — it mirrors what this repository already does in two places:

  • scripts/validate-launch-metrics.mjs:9 checkUrl() — parses and checks
    parsed.protocol !== 'https:'
  • src/lib/profile-links.mjs websiteUrl() — parses, enforces the protocol
    allow-list, and rejects userinfo with the comment "A userinfo component is
    only ever used here to disguise the real host."

tests/validate-awards.test.mjs gains coverage for the userinfo vector (both
on an entry field and on verifiedAgainst), the unparseable-but-https://-prefixed
vector, and a javascript: URL. Two existing assertions move to the new, more
specific error messages; the values they feed are still rejected.

Verification

  • node --test tests/validate-awards.test.mjs — 21/21 pass (was 16)
  • npm run validate:awards — passes on the committed data/awards.json (15 entries)
  • prettier --check clean on both changed files
  • validate:awards already runs in deploy-gh-pages.yml and
    import-architectures.yml, so no workflow change is needed and the tightened
    gate is enforced on merge

Note: tests/awards-timeline.test.mjs fails on a clean main checkout at
8c089a0 as well — that failure is pre-existing and untouched by this PR.

Scope

Limited to scripts/validate-awards.mjs and tests/validate-awards.test.mjs.
Deliberately disjoint from the sibling prefix-test findings elsewhere: the
scripts/validate-radar-reports.mjs entry-url check is claimed by PR #496, and
scripts/validate-case-studies.mjs is claimed by issue #500 / PR #501. Neither
file is touched here.

Closes #507


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

scripts/validate-awards.mjs gated verifiedAgainst, announcementUrl,
caseStudyUrl and talkUrl with /^https:\/\//, which only asserts that a
value begins with https://. It never established that the value parsed as
a URL and never looked at the authority, so it accepted values whose
visible prefix and real host disagree -- https://cncf.io@evil.example
resolves to evil.example -- along with unparseable values such as
https:// and 'https:// evil.example'.

All four fields are rendered as <a href> by AwardsTimeline and
MemberDirectory, so this was the only automated gate standing between
data/awards.json and a clickable outbound link on the published site.

Replace both prefix tests with checkHttpsUrl(), which parses with new
URL(), requires the https: protocol and rejects a userinfo component.
This mirrors checkUrl() in scripts/validate-launch-metrics.mjs and
websiteUrl() in src/lib/profile-links.mjs, which already reject userinfo
as a host-disguise vector.

Two existing assertions move to the new, more specific messages; the
values they feed are still rejected. Adds coverage for the userinfo,
unparseable-but-https-prefixed and javascript: vectors.

npm run validate:awards still passes on the committed data/awards.json
(15 entries).

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.

@mrbobbytables
mrbobbytables added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 15e4420 Sep 23, 2026
2 checks passed
@mrbobbytables
mrbobbytables deleted the sec/awards-url-parse branch September 25, 2026 17:53
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] validate-awards.mjs gates four rendered href fields with an /^https:\/\// prefix test, accepting userinfo host-disguise URLs

1 participant