fix(security): Add security headers and harden external link rendering - #13
fix(security): Add security headers and harden external link rendering#13euxaristia wants to merge 7 commits into
Conversation
vercel.json had no Content-Security-Policy, X-Frame-Options, HSTS, Referrer-Policy, or Permissions-Policy, leaving the site framable and CSP-less as a backstop against markdown/link injection. PeerList, ClonePanel, and HomePage also rendered peer.http_url and clone_url (both sourced from the gossip network / node API, not this app) directly into <a href>, which React does not scheme-filter the way DOMPurify does for the markdown pipeline — a javascript: URL from a malicious peer would have executed on click.
bun update within existing semver ranges. Fixes advisories in dompurify (XSS via detached-subtree hook removal, the markdown pipeline's sanitizer), react-router-dom (open redirect, protocol-validation XSS, inefficient-route-matching DoS), postcss, and nanoid. Not fixed here: sharp (via @vercel/og, server-side OG image generation) and undici (via jsdom, test-only) both need a breaking major-version bump outside this update's semver ranges.
|
@coderabbitai full review |
|
The One thing that supports merging, since it's the first objection I'd expect someone to raise: the CSP does not break syntax highlighting. Two maintenance footguns worth a comment in the code, because both fail silently and neither is obvious six months from now: 1. The sha256 pin and the inline theme script are now coupled. 2. Neither blocks merging. |
The sha256-pinned script-src hash and index.html's inline theme script have to change together, and so do the CSP connect-src allowlist and FEDERATED_NODES — both fail silently in production only, since there's no CSP in dev. vercel.json is strict JSON and can't hold inline comments, so the notes live in CONTRIBUTING.md and beside the node registry instead.
|
@Vasanthdev2004 addressed your earlier requested changes in my latest commit 9159e08 |
|
Thanks for picking up both couplings — the Did a full pass over the rest. One blocking item, then smaller things. Blocking:
|
|
for that ci issue need approval @kevincodex1 |
The scheme check constructed the URL object twice per call. It also had no test coverage, so a later refactor (e.g. to url.startsWith) could silently readmit a rejected scheme like javascript: since nothing pins the current behavior. Refs Gitlawb#13
peerHost() returns "" for any URL whose scheme isHttpUrl blocks, so a peer advertising a javascript: or data: http_url over gossip rendered an empty cell instead of the offending value. That reads as missing data rather than a URL the app refused, which hides exactly the case someone auditing peers needs to see. Render the raw string as text (safe: React escapes it) instead. Refs Gitlawb#13
gitlawbUrl && isHttpUrl(cloneUrl) gated the whole explanatory paragraph, so a clone_url with a rejected scheme dropped a sentence that's true regardless of the https link. Narrow the check to just the anchor, matching the pattern HomePage.tsx already uses. Also bring ClonePanel and HomePage's node-supplied anchors to rel="noopener noreferrer", matching PeerList's — the same class of link was hardened two different ways in this PR. Refs Gitlawb#13
This PR's dependency bump updated bun.lock but not package-lock.json, so npm ci failed on a manifest/lockfile mismatch and CI never ran. The repo only ever used bun.lock in practice — bun was already the working package manager, npm was vestigial and had already drifted once. Rather than regenerate a second lockfile that can drift the same way next time, drop it and standardize CI, README, CONTRIBUTING, and the PR template on Bun. Verified locally: bun install --frozen-lockfile, bun run lint, bun run test (161 passed), and bun run build all pass. Note bun test invokes Bun's own test runner, not Vitest, so docs use `bun run test` to hit the actual package.json script. Refs Gitlawb#13
|
I would like to switch CI to bun. npm is a Pandora's box, I don't even install npm on my systems. The bun feature of no automatic postinstall in particular is valuable here |
|
The security work is in good shape. The Maintainer call: let's split this. Land the security fix now, and take the package-manager migration as its own PR. Of the 7,005 deleted lines, 6,799 are To be clear, the underlying problem is real and I'm the one who raised it — carrying both
So for this PR: Then open the bun migration separately — I'm receptive to it, the two-lockfile situation genuinely needs resolving, and it'll be a quick review once it's the only thing in the diff and a preview deploy is green. Sorry for the extra round trip; the lockfile problem was worth fixing, I just want the fix to land where it can be judged on its own. |
|
Yeah I see your point. I'll leave this to Kevin's discretion cuz it will create more work for me to split this PR |
Summary
vercel.jsonjavascript:/other unsafe URL schemes before rendering peer/repo URLs as<a href>, since those values come from the gossip network and node API, not this appChanges
vercel.json: adds aheadersblock applying CSP,X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policy, and HSTS to every route.script-srcallows'self'plus a sha256 hash for the one inline anti-FOUC theme script inindex.html;style-srckeeps'unsafe-inline'for React's inlinestyleattributes and Google Fonts.index.html: comment noting that editing the inline theme script requires recomputing its CSP hash, or it silently stops running.src/lib/api.ts: newisHttpUrl()guard.src/components/peers/PeerList.tsx:peer.http_url(fromGET /api/v1/peers, i.e. other nodes on the gossip network) now only renders as a link when it'shttp(s); otherwise falls back to plain text. React doesn't scheme-filterhrefthe way the DOMPurify-based markdown pipeline does for links.src/components/repo-detail/ClonePanel.tsx,src/pages/HomePage.tsx: same guard onclone_urlbefore rendering the "clone over https" link.bun.lock,package.json:bun update(semver-respecting). Notablydompurify3.4.11 → 3.4.14 andreact-router-dom7.17.0 → 7.18.2, both of which had open advisories.Not included:
sharp(via@vercel/og, used for OG image generation) andundici(viajsdom, test-only) both need a breaking major-version bump to clear their advisories — out of scope for a semver update.Test plan
bun run lintbun run test— 158 passedbun run build— typecheck + production build succeeddist/index.html's inline theme script still matches the CSPsha256-hashbun audit— dompurify, react-router-dom, postcss, nanoid advisories cleared; sharp and undici remain (documented above)