RootLayout's CSP nonce is always undefined: middleware sets x-nonce on the response, but headers() reads the request — the theme/locale init scripts are blocked
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- next.js, typescript
Direzione di ricerca
Start with middleware.ts:35-38, src/app/layout.tsx:49-68, and the securityHeaders entry in next.config.ts:66-79. Trace how request headers reach headers() and how the CSP reaches the browser, then check the rendered head scripts and response headers. Done means the init scripts and Next.js scripts receive a matching nonce and only the intended CSP is emitted; add a check for that behavior.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Location
src/app/layout.tsx:49-50 (consumer), src/app/layout.tsx:63 and :67-68 (where the nonce is used); the producer is middleware.ts:35-38
Problem
RootLayout expects a per-request CSP nonce from the middleware:
const headerStore = await headers();
const nonce = headerStore.get("x-nonce") ?? undefined;
and then spreads it onto the <html> element and both inline init scripts:
<html lang="en" dir="ltr" suppressHydrationWarning nonce={nonce} ...>
<head>
<script nonce={nonce} dangerouslySetInnerHTML={{ __html: themeInitScript }} />
<script nonce={nonce} dangerouslySetInnerHTML={{ __html: localeInitScript }} />
But the middleware only ever writes the nonce to the response:
const response = NextResponse.next();
response.headers.set("Content-Security-Policy", csp);
response.headers.set("x-nonce", nonce);
return response;
headers() inside a Server Component returns the incoming request headers. Setting a header on the NextResponse does not add it to the request, so headerStore.get("x-nonce") is null on every render and nonce is undefined — permanently. The documented way to pass a value from middleware into a Server Component is to clone the request headers and hand them back through NextResponse.next({ request: { headers } }); that is not done here.
Two consequences follow:
- Both inline scripts render with no
nonceattribute, while the CSP the middleware emits isscript-src 'self' 'nonce-<...>' 'strict-dynamic'— no'unsafe-inline'. A browser enforcing that policy blocksthemeInitScriptandlocaleInitScript. nonceon<html>was never going to work anyway. React'snonceprop applies to<script>and<style>elements; on<html>it just emits a straynonceattribute on the root element. The comment atmiddleware.ts:9-10("React 19 threads it through to all hydration inline scripts") is not how this works — Next.js discovers the nonce from the CSP header on the request, which the middleware never sets either, so Next's own bootstrap/hydration script tags are unnonced too.
Worth noting while fixing this: next.config.ts:66-79 still sends its own Content-Security-Policy header with script-src 'self' 'unsafe-inline', despite the comment at lines 47-51 claiming CSP "is now set in middleware.ts". Two CSP headers are enforced as an intersection, and that second one is also exactly the 'unsafe-inline' policy closed issue #610 was closed on removing. So right now the app is simultaneously shipping the policy #610 removed and a nonce policy whose nonce never arrives.
Why it matters
- With the nonce policy in force and no nonce present,
themeInitScriptandlocaleInitScriptnever execute. The theme is no longer applied before first paint, reintroducing the flash-of-wrong-theme that closed issues #7 and #208 fixed, andlang/dirnever upgrade from the hard-codedlang="en" dir="ltr"— so a right-to-left viewer gets a left-to-right document and a screen reader announces the page in the wrong language, which the comment atlayout.tsx:53-58explicitly promises will not happen. - Because Next's own script tags are unnonced as well, a strictly-enforced nonce policy can block hydration outright, leaving a static page where every interactive control (wallet connect, fund, claim) silently does nothing.
- And the
'unsafe-inline'policy still coming fromnext.config.tsmeans the XSS hardening #610 was closed on is not actually in effect on pages that render untrusted GitHub markdown.
Reproduction
npm run build && npm startwithNEXT_PUBLIC_API_URLset.- Load any page and view source: the two
<script>tags in<head>have nononceattribute, and<html>carries anonceattribute instead. - Check response headers — two
Content-Security-Policyvalues are present: the nonce one frommiddleware.tsand the'unsafe-inline'one fromnext.config.ts. - Strip the
next.config.tsCSP entry so only the middleware policy applies, reload, and the console reports the inline theme/locale scripts refused for violatingscript-src; the page then renders with the wrong theme on first paint.
Suggested fix
In middleware.ts, clone the request headers and pass them through, as well as setting the response header:
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", csp); // so Next nonces its own scripts
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", csp);
Then in src/app/layout.tsx, drop nonce from the <html> element (it does nothing there) and keep it only on the two <script> tags. Finally, delete the Content-Security-Policy entry from securityHeaders in next.config.ts so the nonce policy is the only one in force, matching what that file's own comment already claims.
Add a test or a build-time check that asserts the rendered <head> scripts carry a nonce matching the Content-Security-Policy response header — src/app/layout.tsx has no test file, so nothing currently notices that the nonce is empty.
- Lingua principale
- TypeScript
- Stelle
- 1
- Fork
- 94
- Merge medio
- 1h 2m
- PR unite (30g)
- 25
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di MergeFi/frontend
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
bug testing
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
accessibility bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
MergeFi/frontend#835 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di MergeFi/frontend
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
zapcooking/frontend#946 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
liberation-data/drivine#733 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
makecindy/cindy#10062 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno