Next.js CSP: Nonces, Hashes und statische Seiten


Die Wahl zwischen Nonces und Hashes

Kurzfazit vorweg: Strikte Content Security Policies (CSPs) mit Next.js (insbesondere bei statischer Seitengenerierung / SSG) sind aktuell frustrierend, fehleranfällig und für die allermeisten Projekte den Aufwand nicht wert. Wer nicht zwingend Nonces über serverseitiges Rendering (SSR) erzwingen und auf gewöhnliches Full-Page-Caching verzichten will, fährt mit den standardmäßigen statischen Headern aus der Next.js-Dokumentation wahrscheinlich besser.

Heute habe ich einige Stunden damit verschwendet, strikte CSPs mit Next.js korrekt zum Laufen zu bringen.

Mein Fazit: Lasst es lieber. Es kann funktionieren, aber mit SSG funktioniert es nicht besonders gut (zumindest im Moment).
Klar: Eine schwache CSP ist besser als gar keine, und es hängt auch von den verwendeten Bibliotheken ab. Wahrscheinlich werdet ihr aber "unsafe-inline" für Styles und Skripte erlauben müssen – genau die Dinge, die man aus Sicherheitsgründen eigentlich nicht erlauben sollte. Deshalb würde ich allen Next.js-Entwicklern einfach empfehlen, das standardmäßige statische Beispiel aus der Dokumentation zu verwenden und es dabei zu belassen. Der Aufwand ist die Kopfschmerzen nicht wert, außer man braucht es wirklich unbedingt und ist bereit, den Preis dafür zu zahlen.

Ein bisschen Hintergrund

Hinweis: Ich bin kein Experte auf diesem Gebiet. Das hier ist nur, was ich beim Versuch, es zu implementieren, beim Recherchieren und in Gesprächen mit KI gelernt habe.

Strikte Content Security Policies (CSPs) können den möglichen Schaden von XSS-Angriffen stark begrenzen. Genau das ist im Wesentlichen ihr Zweck. Anders gesagt: CSPs sind ein Werkzeug, mit dem wir verhindern können, dass wir uns versehentlich selbst ins Bein schießen.

Ohne CSP macht man eine Website nicht automatisch anfällig für XSS-Angriffe. Falls es aber eine solche Schwachstelle gibt, ist der mögliche Schaden deutlich größer.


Eine strikte CSP (zur Erinnerung: strikt = gut) vermeidet normalerweise unsafe-inline und verwendet stattdessen Nonces oder Hashes für den absichtlich eingebundenen Inline-Code:

  • Nonces: Der Server hängt an jede Anfrage einer Website eine zufällige Nonce an. Das ist vermutlich der einfachere Weg, erfordert aber SSR. Weil jede Anfrage eine neue Nonce enthalten muss, fällt gewöhnliches Full-Page-Caching weg. Das betrifft nicht automatisch gecachte Daten oder statische Assets. Auf mich wirkt das unnötig, wenn die gesamte Seite sonst statisch wäre (und in serverlosen Umgebungen oft pro Anfrage abgerechnet wird).

  • Hashes: Die andere Möglichkeit ist, die Prüfsummen des erlaubten Inline-Codes in der CSP freizugeben. Das passt grundsätzlich zu SSG. Next.js dokumentiert außerdem experimentelle Subresource Integrity (SRI) für den App Router: Dabei bekommen externe Skripte beim Build ein integrity-Attribut. Das prüft ihren Inhalt, löst aber nicht automatisch die CSP-Freigabe aller Inline-Skripte und -Styles. Eine fertige Lösung für jedes Projekt ist das also nicht.

Welche Option passt wann? Laut aktueller Next.js-Dokumentation erfordert der Nonce-Weg dynamisches Rendering und schließt SSG sowie ISR für diese Seiten aus. Für statische Seiten sind Hashes grundsätzlich passender; die experimentelle SRI-Unterstützung ist aber auf den App Router und beim Build erzeugte Skripte beschränkt. Die Details hängen von Next.js-Version, Router und Build-Setup ab (meine Erfahrung oben ist kein Test aller Kombinationen).

Ein ignoriertes Problem

Die Sache ist: CSPs sind nicht gerade neu. Praktisch alle großen Browser unterstützen CSPs seit 2011. Trotzdem glaube ich, dass die meisten Webentwickler entweder nichts davon wissen oder es ihnen egal ist (man kann schließlich auch ohne CSP sichere Websites bauen).

Das Ergebnis ist, dass praktisch alle großen JavaScript-Frameworks keine oder oft nur sehr lückenhafte CSP-Implementierungen haben. Next.js markiert die SRI-Unterstützung zum Beispiel noch als experimentell. Astros CSP-Unterstützung war bei meinem damaligen Versuch etwas besser, aber ebenfalls noch experimentell.

Auch die Dokumentation zu CSPs ist unabhängig vom Framework oft ziemlich lückenhaft.

Eine Lösung?

Nun, ich muss euch enttäuschen: Ich habe keine. Die traurige Realität ist, dass wir zwischen zwei schlechten Optionen feststecken. Die Unterstützung ist einfach noch nicht da, und einfach auf die bevorzugten Frameworks zu verzichten, ist kaum eine gute Lösung.

Für Next.js-Entwickler gibt es zwei Wege: Entweder ihr verwendet Nonces und verzichtet auf gewöhnliches Full-Page-Caching, oder ihr implementiert hashbasierte Freigaben, was vermutlich einiges an Zeit kostet (es gibt Bibliotheken, die helfen, aber keine fertige Lösung, wie wir Webentwickler sie heute oft erwarten).

Oder ihr probiert Astro aus. Das ist ebenfalls nicht perfekt, aber nachdem ich beide verwendet habe, gefällt mir Astro wohl besser.

Website durchsuchen