CSPs und Next.js

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, wird vollständiges Page-Caching ziemlich schwierig. 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, Hashes zu erzeugen und sie an die jeweiligen Inline-Styles und -Skripte anzuhängen – im Grunde werden Prüfsummen erstellt. Theoretisch funktioniert das gut mit SSG. Der Nachteil? Mit Next.js funktioniert es nicht zuverlässig. Offenbar arbeiten sie daran, aber soweit ich es verstehe, ist das noch nicht fertig implementiert.
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 hashbasierte CSPs zum Beispiel noch als experimentell. Astros CSP-Unterstützung ist meiner Erfahrung nach etwas besser, aber auch dort sind sowohl Nonce- als auch hashbasierte CSPs 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 einen Teil des Cachings, 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.