fix(core-shared): set CSP on forwarded request headers for nonce
Next.js only injects the nonce into its own scripts when it can read it from the request's Content-Security-Policy header. Setting the CSP only on the response left hydration scripts un-nonced in production (A9). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,34 @@ describe("withSecurityHeaders", () => {
|
||||
expect(call[0]?.request?.headers?.get("x-nonce")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("sets the CSP on the forwarded request headers (Next reads the nonce from there)", () => {
|
||||
withSecurityHeaders(makeRequest());
|
||||
|
||||
const call = vi.mocked(NextResponse.next).mock.calls[0] as [
|
||||
{ request?: { headers?: Headers } } | undefined,
|
||||
];
|
||||
const requestCsp = call[0]?.request?.headers?.get(
|
||||
"Content-Security-Policy",
|
||||
);
|
||||
expect(requestCsp).toBeTruthy();
|
||||
expect(requestCsp).toBe(mock._store.get("Content-Security-Policy"));
|
||||
});
|
||||
|
||||
it("request-header CSP carries the nonce in production mode", () => {
|
||||
vi.stubEnv("NODE_ENV", "production");
|
||||
|
||||
withSecurityHeaders(makeRequest());
|
||||
|
||||
const call = vi.mocked(NextResponse.next).mock.calls[0] as [
|
||||
{ request?: { headers?: Headers } } | undefined,
|
||||
];
|
||||
const requestCsp = call[0]?.request?.headers?.get(
|
||||
"Content-Security-Policy",
|
||||
);
|
||||
const nonce = call[0]?.request?.headers?.get("x-nonce");
|
||||
expect(requestCsp).toContain(`'nonce-${nonce}'`);
|
||||
});
|
||||
|
||||
it("returns the NextResponse from NextResponse.next", () => {
|
||||
const result = withSecurityHeaders(makeRequest());
|
||||
|
||||
|
||||
@@ -7,9 +7,16 @@ export function withSecurityHeaders(request: NextRequest): NextResponse {
|
||||
const nonce = generateNonce();
|
||||
const mode = process.env.NODE_ENV === "production" ? "prod" : "dev";
|
||||
const secHeaders = buildSecurityHeaders({ mode, nonce });
|
||||
const csp = secHeaders["Content-Security-Policy"];
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set("x-nonce", nonce);
|
||||
// Next.js only propagates a nonce to its own <script> tags when it can
|
||||
// read it from the *request's* Content-Security-Policy header, so the CSP
|
||||
// must be set on the forwarded request headers — not just the response.
|
||||
if (csp) {
|
||||
requestHeaders.set("Content-Security-Policy", csp);
|
||||
}
|
||||
|
||||
const response = NextResponse.next({
|
||||
request: { headers: requestHeaders },
|
||||
|
||||
Reference in New Issue
Block a user