feat(security): harden CSP and security headers in Next.js web#1060
feat(security): harden CSP and security headers in Next.js web#1060aayushprsingh wants to merge 8 commits into
Conversation
|
Thank you @, for creating the PR and contributing to our UltimateHealth project 💗. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a security-focused Next.js middleware that sets CSP (with per-request nonces) and other hardening headers, plus a CSP violation reporting endpoint and layout changes to consume the nonce.
Changes:
- Introduces
middleware.tsto generate CSP nonces and attach security headers. - Updates
RootLayoutto read a nonce from headers and forces dynamic rendering. - Adds
/api/csp-reportroute to receive CSP violation reports.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| web/src/middleware.ts | Generates CSP nonce and sets CSP + security headers for matched routes. |
| web/src/app/layout.tsx | Reads nonce from headers and forces dynamic rendering to avoid cached nonces. |
| web/src/app/api/csp-report/route.ts | Adds endpoint to receive/log CSP violation reports. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const response = NextResponse.next(); | ||
|
|
||
| // Set CSP and Nonce headers | ||
| // The x-nonce header is used by RootLayout to inject the nonce into rendered scripts | ||
| response.headers.set("Content-Security-Policy", csp); | ||
| response.headers.set("x-nonce", nonce); |
| "style-src 'self' 'unsafe-inline'", | ||
| "font-src 'self'", | ||
| "img-src 'self' blob: data: https://raw.githubusercontent.com https://github.com https://user-images.githubusercontent.com", | ||
| "connect-src 'self'" + (isDev ? " http: https: ws:" : " https:"), |
| "object-src 'none'", | ||
| "base-uri 'self'", | ||
| "frame-ancestors 'none'", | ||
| "report-uri /api/csp-report", |
| // HSTS: 2 years duration, include subdomains and enable preloading | ||
| response.headers.set( | ||
| "Strict-Transport-Security", | ||
| "max-age=63072000; includeSubDomains; preload" | ||
| ); |
| // Cross-Origin Isolation Policies | ||
| response.headers.set("Cross-Origin-Opener-Policy", "same-origin"); | ||
| response.headers.set("Cross-Origin-Resource-Policy", "same-origin"); |
| }; | ||
|
|
||
| export default function RootLayout({ | ||
| export default async function RootLayout({ |
| children: React.ReactNode; | ||
| }) { | ||
| // Read the nonce injected by middleware so Next.js can use it for inline scripts | ||
| const nonce = (await headers()).get("x-nonce") ?? undefined; |
| export async function POST(request: Request) { | ||
| try { | ||
| const report = await request.json(); | ||
|
|
||
| // Log the violation report | ||
| // In production, consider using a service like Sentry, Datadog, or a custom logger | ||
| console.warn("CSP Violation Reported:", report); | ||
|
|
||
| return NextResponse.json({ status: "ok" }, { status: 200 }); | ||
| } catch (error) { | ||
| console.error("Error processing CSP report:", error); | ||
| return NextResponse.json({ error: "Failed to process report" }, { status: 400 }); | ||
| } |
|
/review |
🤖 Gemini AI Code ReviewSummaryThis Pull Request introduces a comprehensive set of security enhancements to the UltimateHealth Next.js application, primarily focusing on Content Security Policy (CSP) and various HTTP security headers. The changes include a new middleware for header injection, a dedicated API endpoint for CSP violation reporting, and modifications to the root layout to support CSP nonces. The PR demonstrates a strong understanding of modern web security best practices, particularly with the implementation of 🔴 High Severity
🟡 Medium Severity
🟢 Low Severity / Nits
What's Good ✅
VerdictRequest Changes The most critical issue is the CSP Nonce Not Applied to HTML Element. Without this fix, the application will likely be non-functional in a production environment with the enabled CSP. Addressing this, along with the other high and medium severity issues, is essential before merging. |
SB2318
left a comment
There was a problem hiding this comment.
Thanks for your contribution!
But I would like to suggest some fixes.
- Make sure you applied all suggestions provided by Copilot
- The CSP nonce generated in middleware is read in layout.tsx, but I don't see it being applied to the element or any script tags. With a nonce-based CSP (script-src 'nonce-...' 'strict-dynamic'), Next.js needs access to the nonce for its inline scripts. Can you confirm the nonce is actually propagated to the rendered HTML? Otherwise client-side hydration may fail in production.
- The CSP report endpoint currently relies on request.json() and broad error handling. Consider adding explicit handling for malformed payloads and returning a more specific response for invalid reports. This would make debugging and monitoring easier in production.
- CSP reports may contain URLs, referrers, or other potentially sensitive information. Before integrating these logs into monitoring systems, consider sanitizing or limiting the fields that are recorded.
- Using force-dynamic is a deliberate trade-off when implementing per-request nonces. The performance impact is worth noting, but I don't think it should block this PR unless there is evidence of a measurable regression.
9ffafe4 to
9b86337
Compare
- Add src/middleware.ts with full Content Security Policy implementation:
- Nonce-based CSP in production (strict-dynamic + nonce-{uuid})
- Development-friendly CSP with unsafe-eval/unsafe-inline for HMR
- HSTS with 2-year max-age, subdomains, and preloading
- X-Frame-Options: DENY, X-Content-Type-Options: nosniff
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy restricting camera, microphone, geolocation, etc.
- Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy: same-origin
- CSP report-uri pointing to /api/csp-report
- Add src/app/api/csp-report/route.ts for CSP violation logging
- Parses violation reports and logs to server console
- Returns 204 No Content to avoid feedback loops
- Update src/app/layout.tsx:
- Import headers() from next/headers to read x-nonce
- Make RootLayout async to await headers()
- Export dynamic = 'force-dynamic' for unique nonce per request
- Read nonce from x-nonce header set by middleware
Closes SB2318#1047
- Fix CSP nonce implementation by injecting it into request headers - Add CSP reporting endpoint and directive - Minimize resource allowlists for img-src and connect-src - Expand Permissions-Policy coverage - Replace legacy proxy.ts with hardened middleware.ts - Fix formatting in next.config.ts and package.json
… and restore CSP security hardening
9b86337 to
e852634
Compare
Summary
Hardens the Content Security Policy (CSP) and security headers configuration for the UltimateHealth Next.js web app (
web/), addressing the issues identified in #1047.Changes
web/src/middleware.ts(new)Implements a robust security middleware with:
'nonce-{uuid}'+'strict-dynamic'for script-src; development allows'unsafe-eval'/'unsafe-inline'for HMR without breaking DX.max-age=63072000; includeSubDomains; preload(2-year, preload-list eligible).same-originfor cross-origin isolation./api/csp-report.web/src/app/api/csp-report/route.ts(new)A lightweight API endpoint that:
application/csp-reportPOST requests.204 No Contentto avoid response feedback loops.web/src/app/layout.tsx(modified)headers()fromnext/headersto read thex-nonceheader injected by middleware.RootLayoutasync to await the headers.dynamic = 'force-dynamic'to ensure a unique nonce is generated per request (prevents nonce reuse from cached responses).Testing
npm run devinsideweb/— no console errors, app loads normally.Content-Security-Policy,Strict-Transport-Security,X-Frame-Options, etc./api/csp-reportreceives the violation.Fixes #1047