Skip to content

feat(security): add CSP + validate third-party thumbnail + encode id - #34

Merged
EnesYilmazcode merged 2 commits into
mainfrom
claude/funny-hawking-930hed
Jul 15, 2026
Merged

EnesYilmazcode merged 2 commits into
mainfrom
claude/funny-hawking-930hed

Conversation

@EnesYilmazcode

Copy link
Copy Markdown
Owner

Fixes #18.

Defense-in-depth hardening (no active vuln — the app is keyless and renders all external strings as escaped React text).

Changes

  • Content-Security-Policy — injected into index.html for production builds only (dev/HMR needs inline + eval) via a small Vite transformIndexHtml plugin. Locks default-src to 'self', restricts img-src to YouTube's CDNs, connect-src to noembed + lrclib, media-src to self + blob, forbids object-src/base-uri. style-src keeps 'unsafe-inline' for React's inline style attributes (never for script).
  • Thumbnail validationYouTubeLinkCard validates the noembed-supplied thumbnail (https + a ytimg/ggpht host) before using it as <img src>; drops it otherwise (card still shows title/author).
  • Encode idencodeURIComponent the id in the noembed request URL (a no-op for a valid 11-char id, but prevents query-param injection from a hypothetical unvalidated id).

Why build-only CSP

A <meta> CSP in index.html applies in dev too and would break Vite's HMR (inline scripts + eval). Injecting it only for command === 'build' keeps npm run dev working while shipping the policy to production.

Verification

Verified against the production build served under /melody/ (matching the real deploy base):

Check Result
App renders under CSP (script-src 'self')
Styles apply (body bg = theme, React inline styles)
Font loads (font-src 'self')
media-src blob: audio not blocked
CSP violations / console errors none

Confirmed there are no inline <script>s in the built HTML (both the module bundle and registerSW.js are external), so script-src 'self' is sufficient.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 2 commits July 15, 2026 01:08
Defense-in-depth hardening (no active vuln — the app is keyless and renders all
external strings as escaped React text):

- Content-Security-Policy: injected into index.html for PRODUCTION BUILDS ONLY
  (dev/HMR needs inline+eval) via a small Vite plugin. Locks default-src to
  'self', restricts img to YouTube's CDNs, connect to noembed+lrclib, media to
  self+blob, and forbids object/base-uri. style-src keeps 'unsafe-inline' for
  React's inline style attributes (never for script).
- YouTubeLinkCard: the thumbnail URL comes from noembed's response, so validate
  it (https + a ytimg/ggpht host) before using it as an <img src>; drop it
  otherwise (the card still shows title/author).
- youtube.js: encodeURIComponent the id in the noembed request URL — a no-op for
  a valid 11-char id, but prevents query-param injection if an unvalidated id
  ever reaches it.

Verified against the PRODUCTION build served under /melody/: app renders,
styles/fonts apply, media blob: URLs play, and there are zero CSP violations or
console errors.

Fixes #18.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkDKh1Uo1a4D7n5wCKdcKF
Review follow-ups: inject the CSP meta as the first head child so it governs
the app bundle/CSS/font fetches (a meta CSP doesn't apply to subresources
requested before it's parsed); and require a *.ytimg.com / *.ggpht.com
subdomain in safeThumb to match the img-src allowlist (the CSP doesn't permit
the bare apex). Re-verified against the production build: renders, styles/fonts
apply, zero CSP violations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkDKh1Uo1a4D7n5wCKdcKF
@EnesYilmazcode
EnesYilmazcode merged commit 0421e5c into main Jul 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MEDIUM] Security hardening: add CSP, validate the noembed thumbnail src, encode the noembed id

2 participants