Skip to content

feat(web): report-only content security policy with a per-request script nonce - #1318

Open
JOY (JOY) wants to merge 2 commits into
devfrom
feat/1311-csp-report-only
Open

JOY (JOY) wants to merge 2 commits into
devfrom
feat/1311-csp-report-only

Conversation

@JOY

@JOY JOY (JOY) commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Refs #1311 (report-only step; enforcing is a later change after the report window)

What

  • src/lib/csp.ts: the policy. script-src 'self' 'nonce-<per request>' 'strict-dynamic' (plus 'unsafe-eval' in development only), default-src 'self', styles 'self' 'unsafe-inline', images https: (shop CDNs, as images.remotePatterns), connect-src adds the Supabase project, object-src 'none', the existing frame-ancestors, base-uri, form-action, and report-uri /api/csp-report.
  • src/proxy.ts: a fresh 128-bit nonce per page request, sent as Content-Security-Policy-Report-Only on the response and on the forwarded request, where Next.js 16 reads it (it accepts the report-only header) and puts the nonce on its own scripts. The enforced next.config.mjs CSP (frame-ancestors and friends) is unchanged.
  • POST /api/csp-report: logs a violation (directive, blocked URL, page, source; origin and path only, no query string) and always answers 204. The body is read as a stream and dropped past 8 KB (with or without Content-Length); a report needs a directive and a page on this site's origin; each distinct violation is logged once per 10 minutes per instance, at most 200 distinct ones, so the public endpoint cannot flood the logs.

Compatibility

  • Inline JSON-LD (<script type="application/ld+json">) is a data block, not a script: script-src does not apply, so no nonce is needed.
  • Vercel analytics: the app loads none today; a /_vercel/* script added later and injected by the nonced runtime is allowed by 'strict-dynamic'.
  • Known gap for the report window: a page served from the ISR / static cache was rendered without a request, so its Next.js inline scripts carry no nonce and will show up as script-src-elem reports. Enforcing therefore needs a decision once the reports are in: render those pages per request (nonce) or switch the policy to hashes. Report-only changes nothing for visitors meanwhile.

Tests

src/lib/__tests__/csp.test.ts (added to pnpm --filter web test): the directives, a fresh base64 nonce per request, the same policy on the response and on the forwarded request (x-middleware-request-*), no enforced CSP header, and the report endpoint logging without query strings, answering 204 to garbage, logging a repeated violation once, ignoring a report without a directive or for another site, and stopping an oversized streamed body within 16 KB.

Review gate

Codex round 1: the 8 KB cap did not hold for a streamed body without Content-Length, and any {"csp-report":{}} post was logged (log flooding): both fixed with tests. It also confirmed the ISR point above (cached HTML keeps the nonce it was rendered with, or none), which stays the open decision before enforcing.

Checks

pnpm --filter web test, tsc --noEmit, eslint, prettier: green locally.

🤖 Generated with Claude Code

…ipt nonce

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
rate Ready Ready Preview Oct 28, 2026 7:46pm UTC

Request Review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 00bbb887-09a8-440a-90f4-87670cd6728f
Commits

Reviewing files that changed from the base of the PR and between 4eca23d and a2c982f.

Files selected for processing: 5
  • apps/web/package.json
  • apps/web/src/app/api/csp-report/route.ts
  • apps/web/src/lib/__tests__/csp.test.ts
  • apps/web/src/lib/csp.ts
  • apps/web/src/proxy.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

dos[bot]
dos Bot approved these changes Oct 28, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (5 eligible files · 19,465 chars · 1 model call(s) · 1 PR unit(s))

Whole diff with bounded context at base 4eca23d / head b41eaa3.

✅ CLEAN_PASS: Complete diff reviewed

…upes before logging

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dos[bot]
dos Bot approved these changes Oct 28, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (5 eligible files · 23,069 chars · 2 model call(s) · 1 PR unit(s))

Whole diff with bounded context at base 4eca23d / head a2c982f.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

This branch was successfully deployed

1 active deployment
Preview — a2c982f7 Deployed Oct 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:frontend Frontend / Next.js / UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant