Skip to content

fix(security): add CSP and X-Frame-Options headers to nginx.conf - #685

Merged
birme merged 1 commit into
Eyevinn:mainfrom
Yovieee:fix/nginx-csp-xframe-624
Sep 17, 2026
Merged

birme merged 1 commit into
Eyevinn:mainfrom
Yovieee:fix/nginx-csp-xframe-624

Conversation

@Yovieee

@Yovieee Yovieee commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Problem

nginx/nginx.conf sets no browser security headers except HSTS — no clickjacking protection (X-Frame-Options) and no XSS/exfiltration baseline (Content-Security-Policy). Fixes #624.

Approach

Tests

Risk

Low. Header-only addition at server level; no app code touched. If the CSP proves too strict for any inline assets, style-src 'unsafe-inline' is already allowed per the issue and can be tightened with nonces later.

Fixes #624

@birme birme left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Verdict: Needs Changes

Summary: A clean, minimal, header-only change that faithfully implements issue #624 and correctly follows the merged HSTS precedent (always, server-level inheritance). No security backdoors, no weakened validation, no secrets, no scope creep — the external contribution is honest and well-scoped. However, the CSP is inconsistent with the CSP already shipped in index.html and is stricter in connect-src and img-src in ways that will break a real, user-facing feature (the ws:// WebSocket control channel). This is a Blocking correctness issue plus two Warnings, so it needs changes before merge.


Blocking

  • nginx/nginx.conf:5 — The new connect-src 'self' wss: https: omits ws: (insecure WebSocket), but the app ships a WebSocket control feature whose modal defaults to ws://. In src/components/calls-page/connect-to-ws-modal.tsx:56 the default is const PROTOCOL = "ws://" with placeholder localhost:12345, and src/hooks/use-websocket.ts opens new WebSocket(url) directly. Because CSP headers combine restrictively with the existing <meta http-equiv="Content-Security-Policy"> in index.html (which explicitly allows connect-src 'self' ws: wss: http://localhost:* https:), adding this stricter header makes the effective policy the intersection — dropping ws: and http://localhost:*. Any user connecting to a ws:// endpoint (the modal's default, used for Stream Deck/Companion control per the linked docs) will be blocked by CSP. Required: align the nginx connect-src with the shipped meta CSP — at minimum connect-src 'self' ws: wss: https: (add http://localhost:* if local-dev serving through this nginx config is intended). Copying #624's recommendation verbatim is the root cause; #624's table predates/overlooks the ws:// need that index.html already encodes.

Warnings

  • nginx/nginx.conf:5 — CSP has no img-src, so image loading falls back to default-src 'self', which disallows data: URIs. The shipped index.html meta CSP explicitly grants img-src 'self' data:. The only current data:image reference is in README.md (not runtime), so app breakage risk is low today, but this is a silent divergence from the intended policy and will bite the moment any inline SVG/data-URI icon is used. Add img-src 'self' data: to match index.html.

  • nginx/nginx.conf:5 — Two CSPs will now be delivered for the same document: this HTTP header and the pre-existing <meta http-equiv="Content-Security-Policy"> in index.html. The browser enforces both simultaneously (most-restrictive-wins per directive), which is the mechanism behind the Blocking issue above and is confusing to maintain. Recommend consolidating to a single source of truth — either remove the meta CSP from index.html and make the nginx header a superset of what the app needs, or keep only the meta tag. At minimum, the two must not contradict each other.


Suggestions

  • nginx/nginx.conf:5style-src 'unsafe-inline' is necessary today for Emotion CSS-in-JS injected <style> tags and is correctly included (not a defect). Note for a future hardening pass: Emotion supports nonce-based CSP, which would let you drop 'unsafe-inline'.
  • PR body — The claim that location / inherits the server-level headers is correct only because location / defines no add_header of its own (nginx add_header inheritance is all-or-nothing per context). If any future location block adds its own header, these three will silently stop applying there.
  • statusCheckRollup is empty (no CI configured/triggered for this change). Since nginx -t was not run (per PR body), a maintainer should validate the config loads before merge.

Security assessment (external-contributor scrutiny)

  • No backdoors, no eval/new Function, no hardcoded secrets or tokens.
  • No weakened validation — the change is purely additive security headers.
  • No scope creep: exactly two headers added, matching the PR title and #624; X-Content-Type-Options and Referrer-Policy are correctly deferred to PR #682.
  • The CSP is neither meaninglessly loose (no 'unsafe-eval', no wildcard default-src *, no frame-ancestors *) nor gratuitously strict beyond the two connect-src/img-src gaps flagged above. X-Frame-Options: DENY is appropriate for this app.

@birme

birme commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Housekeeping update (daily-backlog-pr):

I approved the first-time-contributor CI gate, and all checks are now green (builds, lint, prettier, tests). Thanks for the contribution.

However, the CHANGES_REQUESTED review from 2026-09-15 is still unaddressed, so this stays blocked pending a fix — green CI doesn't clear it. The blocking item is real: the nginx connect-src 'self' wss: https: omits ws:, and because it combines most-restrictively with the existing <meta http-equiv="Content-Security-Policy"> in index.html (which allows connect-src 'self' ws: wss: http://localhost:* https:), the effective policy drops ws: — breaking the WebSocket control channel whose modal defaults to ws:// (src/components/calls-page/connect-to-ws-modal.tsx). Please align connect-src to at least 'self' ws: wss: https: and add img-src 'self' data: to match the shipped meta CSP (see the earlier review for the full detail). Once pushed, this is good to merge.

@birme birme left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed via daily-backlog-pr (Phase 3.5, orphan-PR housekeeping). Right direction for #624 and CI is green, but the CSP would introduce functional regressions versus the policy already shipped in index.html's <meta http-equiv="Content-Security-Policy">:

Needs Changes

  • connect-src 'self' wss: https: omits ws:, which the existing meta CSP includes. Since both CSPs are enforced simultaneously (browser takes the intersection), plain-ws:// connections used in dev / non-TLS deployments would be blocked. Add ws:.
  • No img-src directive, so it falls back to default-src 'self' and drops the data: images the existing meta CSP allows (img-src 'self' data:). Add img-src 'self' data:.
  • Dual CSP hazard: this adds a second CSP via nginx while index.html still carries a meta CSP. Please remove the meta CSP (preferred, so the header is canonical) or document why both exist, to avoid silent drift.

X-Frame-Options: DENY, style-src 'unsafe-inline' (needed for Emotion), media-src blob:, and the always flags are all correct.

@birme

birme commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

daily-backlog-pr Phase 2 re-entry: this PR is both merge-conflicting with main and carrying unaddressed "changes requested" review(s). Because it originates from a fork, this automated pass will not force-push a rebase into a third party's personal fork. update-branch also won't help here since the branch genuinely conflicts (not just stale).

Routing to a human/author: please rebase fix/nginx-csp-xframe-624 onto main, resolve conflicts, and address the outstanding review feedback. Leaving the board item in Ready.

@birme
birme force-pushed the fix/nginx-csp-xframe-624 branch from 8b1e93b to 2a42e77 Compare September 17, 2026 13:41

@birme birme left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — LGTM

Minimal, well-targeted security hardening: adds X-Frame-Options: DENY and a Content-Security-Policy to the production nginx.conf (+2/-0). CI green.

The CSP is sound — it mirrors the app's already-shipped index.html meta CSP, keeps script-src 'self' strict (no unsafe-inline/unsafe-eval), and its one relaxation (style-src 'unsafe-inline') is genuinely required by the Emotion CSS-in-JS runtime. connect-src correctly permits WebRTC signaling/API traffic across the OSC (https://$OSC_HOSTNAME/) and reverse-proxy (/) deployment models. No backdoors, secrets, weakened validation, or scope creep; nothing removed from the existing header set.

Blocking: None.

Suggestions (non-blocking, follow-up):

  • The CSP now lives in two places (this add_header and the index.html meta tag) — a future drift hazard; consider making the nginx header the single source of truth.
  • Optional later hardening: add frame-ancestors 'none', base-uri 'self', form-action 'self'.

Approving on review. Note: this repo's main currently reports mergeStateStatus: BLOCKED on every PR (an org base-branch policy beyond the visible classic branch protection), so the actual merge needs a human/admin — flagging rather than force-merging.

@birme
birme merged commit 46a9f97 into Eyevinn:main Sep 17, 2026
4 checks 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.

Security: Add Content-Security-Policy and X-Frame-Options headers to nginx.conf

2 participants