Skip to content

fix(community): validate profile website links before rendering - #273

Open
kubestellar-hive[bot] wants to merge 1 commit into
mainfrom
sec/profile-link-validation
Open

kubestellar-hive[bot] wants to merge 1 commit into
mainfrom
sec/profile-link-validation

Conversation

@kubestellar-hive

Copy link
Copy Markdown
Contributor

Security Fix

src/components/CommunityPeople/index.js built the "Website" link of a member
lightbox with a startsWith("http") substring test and, when that test failed,
concatenated the value onto https://.

That value is not repository data. scripts/fetch-community-people.mjs copies
the free-text blog field of a third-party GitHub profile verbatim into
data/community-people.json, and .github/workflows/refresh-community-people.yml
commits the result weekly without anyone editing it. So a value such as
www.cncf.io@attacker.example/login became
https://www.cncf.io@attacker.example/login — a published outbound link whose
visible prefix and real authority disagree.

This change resolves the value through the URL parser instead of testing a
prefix:

  • only http: and https: are accepted; anything else is dropped
  • a value with no scheme is treated as an https host, not as a string to
    concatenate, so the userinfo trick no longer works
  • an explicit userinfo authority (user@host) is rejected outright
  • the github / linkedin / twitter handles that profileUrl()
    interpolates are percent-encoded, so a handle cannot add path segments

An explicit http:// value is preserved rather than silently rewritten to
https:// — rewriting a person's stated URL can break the link, and the
protocol allowlist is the security control here. data/community-people.json
currently exercises both live paths: abebars.io (bare host) and
http://wangxu.me (explicit http).

javascript: was never exploitable here — it failed the old startsWith
test and got the https:// prefix, which neutralised it. This is an
unvalidated-redirect / link-spoofing fix, not an XSS fix, and the issue says so.

Files and cluster claimed

  • src/components/CommunityPeople/index.js (PersonDialog links array)
  • src/lib/profile-links.mjs (new — websiteUrl(), profileUrl())
  • tests/profile-links.test.mjs (new)

Disjoint from the open PRs: #249 fixes the same substring-vs-URL class in
scripts/import-architectures.mjs renderProjectCards, #266 touches
scripts/lib/project-assets.mjs, #214 adds tests for
scripts/fetch-community-people.mjs without changing it, and #268 covers
useFocusTrap. None of them touches src/components/CommunityPeople/index.js
or src/lib/.

The helper is a plain ESM module rather than a React module specifically so
node --test can import it without a renderer.

Verification

  • node --test tests/profile-links.test.mjs — 12/12 pass
  • npm run test:unit — 67/67 pass
  • npm run build[SUCCESS] Generated static files in "build"; the bundle
    contains the new helper and no longer contains the startsWith("http") guard

Closes #272


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

The community lightbox built its Website link with a startsWith("http")
test and, when that failed, concatenated the value onto "https://".
The value is the free-text blog field of a third-party GitHub profile,
copied verbatim into data/community-people.json by the scheduled refresh
and never reviewed, so a value such as trusted.example@attacker.example
produced a link whose visible prefix and real authority disagree.

Resolve the value through the URL parser instead: accept only http and
https, treat a scheme-less value as an https host rather than a string to
concatenate, and reject a userinfo authority. Percent-encode the social
handles profileUrl interpolates so a handle cannot add path segments.

The helper lives in src/lib/profile-links.mjs so it is importable by
node --test without a React renderer.

Closes #272

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

@kubestellar-hive kubestellar-hive Bot added hold 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 18, 2026
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

Development

Successfully merging this pull request may close these issues.

[sec-check] Third-party GitHub profile 'blog' value is rendered as a link href without scheme validation (src/components/CommunityPeople/index.js)

0 participants