Die Content Security Policy (CSP) ist ein HTTP-Antwort-Header, mit dem ein Server dem Browser vorgibt, aus welchen Quellen eine Seite Skripte, Stylesheets, Bilder, Schriften, Frames und Netzwerkverbindungen laden darf. Alles, was nicht ausdrücklich erlaubt ist, blockiert der Browser. Die CSP ist damit der wirksamste Schutz gegen Cross-Site-Scripting und Content-Injection im Client und zugleich der einzige Security Header, der einen laufenden Onlineshop beschädigen kann, wenn er falsch gesetzt wird. Spezifiziert wird die CSP vom W3C; die aktuelle Fassung ist Content Security Policy Level 3.
Wie eine Content Security Policy aufgebaut ist
Eine CSP besteht aus Direktiven, die jeweils einen Ressourcentyp abdecken, gefolgt von einer Liste erlaubter Quellen. Direktiven werden mit Semikolon getrennt. Ein einfaches Beispiel:
Content-Security-Policy: default-src 'self'; script-src 'self' www.googletagmanager.com; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
Gelesen heißt das: Alles kommt standardmäßig nur von der eigenen Origin. Skripte zusätzlich vom Tag-Manager-Host. Bilder auch als Data-URL. Plugins wie Flash oder Java, die über <object> eingebunden würden, gar nicht. Das <base>-Element darf nur auf die eigene Origin zeigen, und die Seite darf nur von der eigenen Origin in einen Frame eingebettet werden.
Die wichtigsten Direktiven lassen sich in drei Gruppen einteilen. Die erste Gruppe steuert Ressourcen: script-src, style-src, img-src, font-src, connect-src (XHR, fetch, WebSockets), frame-src und media-src. default-src ist der Fallback für alle Ressourcen-Direktiven, die nicht explizit gesetzt sind. Die zweite Gruppe steuert das Dokument selbst: base-uri, form-action (wohin Formulare gesendet werden dürfen), frame-ancestors (wer die Seite einbetten darf) und object-src. Die dritte Gruppe betrifft das Reporting: report-uri und das neuere report-to nennen den Endpunkt, an den der Browser Verstöße meldet.
Quellenangaben: von 'self' bis 'strict-dynamic'
Als Quelle kann eine Direktive Hostnamen (js.stripe.com, auch mit Wildcard wie *.paypal.com), Schemata (https:, data:) und eine Reihe von Schlüsselwörtern in einfachen Anführungszeichen enthalten. 'self' steht für die eigene Origin, 'none' verbietet alles. 'unsafe-inline' erlaubt Inline-Skripte und Inline-Styles, also <script>-Blöcke ohne src, onclick-Attribute und style-Attribute. 'unsafe-eval' erlaubt eval() und verwandte Funktionen.
Weil 'unsafe-inline' im script-src den Schutz gegen XSS praktisch aufhebt, kennt die Spezifikation zwei Alternativen. Eine Nonce ist ein pro Request zufällig erzeugter Wert, den der Server sowohl in den Header ('nonce-abc123') als auch in jedes legitime <script nonce="abc123"> schreibt; ein eingeschleustes Skript kennt den Wert nicht und wird blockiert. Ein Hash ('sha256-…') erlaubt genau einen Inline-Block mit bekanntem Inhalt. 'strict-dynamic' schließlich besagt: Jedes Skript, das ein per Nonce oder Hash freigegebenes Skript nachlädt, ist ebenfalls erlaubt; Host-Allowlists werden dann ignoriert. Auf dieser Kombination beruht die sogenannte Strict CSP.
Warum Host-Allowlists nicht reichen
Die intuitive CSP ist eine Liste vertrauenswürdiger Hosts. Dass dieser Ansatz in der Breite scheitert, hat ein Google-Forschungsteam 2016 in der Studie „CSP Is Dead, Long Live CSP!" belegt. Die Autoren untersuchten 26.011 im Web ausgelieferte Policies und fanden, dass 94,68 Prozent der Policies, die Skripte einschränken wollen, umgehbar sind. Der Grund: Sobald ein erlaubter Host irgendwo einen JSONP-Endpunkt oder eine Bibliothek wie Angular hostet, lässt sich darüber beliebiger Code ausführen, obwohl die Policy formal korrekt ist. Große CDNs und Analytics-Hosts sind in diesem Sinn fast immer verwundbar. Die Empfehlung aus der Studie ist die Strict CSP mit Nonce und 'strict-dynamic', die ohne Hostliste auskommt.
Dass sich das in der Praxis nur langsam durchsetzt, zeigt der HTTP Archive Web Almanac 2025: 21,9 Prozent der mobil gecrawlten Seiten senden überhaupt eine CSP. Von den ausgelieferten Policies mit script-src enthalten 92 Prozent ein 'unsafe-inline'. Nonces nutzen rund 20 Prozent, 'strict-dynamic' rund 10 Prozent. Der überwiegende Teil der CSPs im Web schützt also nicht gegen XSS, sondern bestenfalls gegen Clickjacking und Plugin-Einbettung.
Die Zahlen sind kein Grund, die CSP wegzulassen. Sie sind ein Grund, die Direktiven zu trennen: Die Dokument-Direktiven object-src 'none', base-uri 'self', frame-ancestors 'self' und form-action 'self' brechen in einer normalen Web-Anwendung nichts und schließen jeweils eine konkrete Angriffsklasse. Das script-src ist ein eigenes Projekt.
CSP im Onlineshop: das Checkout-Problem
Ein Onlineshop ist aus Sicht einer CSP ein Dokument voller fremder Skripte. Der Checkout-Button von PayPal, das Eingabefeld von Stripe, der Google Tag Manager, der Cookie-Banner, Bewertungs-Widgets und Chat-Tools laden alle von fremden Hosts. Ein script-src 'self' ohne diese Hosts bedeutet: Die Seite lädt, der Warenkorb funktioniert, und im Checkout erscheint kein einziger Zahlungsbutton. Im Browser-Log steht die Ursache, im Monitoring steht nur ein Umsatzeinbruch.
Die Hosts stammen aus der Dokumentation der Anbieter, nicht aus dem Raten. PayPal nennt für sein JS SDK *.paypal.com, *.paypalobjects.com und *.venmo.com für script-src, frame-src, connect-src, img-src und style-src. Stripe listet js.stripe.com, *.js.stripe.com, hooks.stripe.com und api.stripe.com. Ein unterschätzter Posten ist form-action: Zahlungswege, die per Formular-Redirect arbeiten (3-D Secure, Rechnungskauf-Anbieter), brauchen den Host des Payment Service Providers in dieser Direktive. Fehlt er, klickt der Kunde „Jetzt kaufen", der Browser verweigert den Redirect, und die Zahlung kommt nicht zustande.
Report-Only als Pflichtschritt
Der Header Content-Security-Policy-Report-Only wertet eine Policy aus, blockiert aber nichts. Jeder Verstoß landet in der Browser-Konsole und, wenn ein report-uri gesetzt ist, als JSON an einem Report-Endpunkt. Für einen Shop ohne CSP ist das der einzige vertretbare Einstieg. Zwei Wochen Report-Only über alle Seitentypen, also Startseite, Kategorie, Produkt, Warenkorb, Checkout, Kontoseiten und Bestellbestätigung, zeigen, welche Hosts wirklich gebraucht werden. So lange dauert es, bis auch seltene Zahlungswege und Kontoseiten, die nur alle paar Tage jemand öffnet, einmal durchgelaufen sind. Erst danach wird aus -Report-Only die scharfe Policy. Beide Header lassen sich parallel senden: eine scharfe Policy mit den sicheren Direktiven und daneben eine Report-Only-Policy, die das strengere script-src testet.
Zum Prüfen einer fertigen Policy gibt es zwei Werkzeuge, die zusammengehören. Der CSP Evaluator von Google bewertet den Header-Text statisch und markiert umgehbare Hosts, fehlendes object-src oder ein base-uri, das nicht gesetzt ist. Die DevTools des Browsers zeigen dagegen, was auf der konkreten Seite tatsächlich blockiert wurde; die Konsole nennt dabei die verletzte Direktive wörtlich, etwa Refused to load the script 'https://…' because it violates the following Content Security Policy directive: "script-src 'self'". Wer beides einmal pro Release durchläuft, bemerkt ein neues Plugin-Skript oder einen geänderten Payment-Host, bevor die Kunden es tun. Report-Endpunkte wie report-uri.com oder ein eigener Collector sammeln dieselben Meldungen aus den Browsern echter Besucher und sind damit die einzige Quelle für Seiten und Zahlungswege, die im eigenen Test nie vorkommen.
CSP in Shopware 6
Shopware 6 unterscheidet drei Bereiche. Die Administration bekommt eine Nonce-basierte Policy, API-Routen ein restriktives Default-Template. Für den Storefront enthält der Parameter shopware.security.csp_templates einen leeren String; der Core liefert also bewusst keine Storefront-CSP aus. Der Grund liegt in den Templates: Der Storefront schreibt in jede Seite Inline-Skripte, etwa window.activeNavigationId und die Router-Konfiguration in layout/meta.html.twig. Shopware erzeugt zwar pro Request eine Nonce im Request-Attribut _cspNonce, die Storefront-Templates tragen sie aber nicht in die <script>-Tags ein. Ein script-src ohne 'unsafe-inline' oder passende Nonce bricht den Storefront deshalb auf jeder Seite. Eine echte Nonce-CSP für den Storefront ist ein Template-Projekt, kein Header-Handgriff: Jedes Theme, jedes Plugin und jede App, die Inline-Skripte ausgibt, muss die Nonce mitführen.
Für die Praxis heißt das: Die Dokument-Direktiven sofort am Webserver setzen, das script-src mit den Hosts der eingesetzten Dienste und vorerst mit 'unsafe-inline' als Report-Only ausrollen, und die Nonce-Umstellung als eigenes Vorhaben planen. Wer die CSP setzt, ohne das Theme zu kennen, stoppt den Shop. frame-ancestors ersetzt dabei X-Frame-Options in allen aktuellen Browsern; beide zusammen zu setzen ist die saubere Übergangslösung, zumal Shopware X-Frame-Options: deny ohnehin im Core sendet.
Abgrenzung und Grenzen
Die CSP ist keine Serverschutzmaßnahme. Sie verkleinert die Angriffsfläche im Browser des Nutzers; eine SQL-Injection, ein ungepatchtes Plugin oder ein offener Admin-Zugang bleiben davon unberührt. Sie ersetzt auch nicht die Ausgabekodierung im Template. Twig in Shopware maskiert Variablen standardmäßig, und genau diese Maskierung verhindert, dass ein eingeschleuster String überhaupt als Skript im Dokument landet. Die CSP ist die zweite Verteidigungslinie für den Fall, dass die erste versagt, etwa durch ein |raw-Filter an der falschen Stelle.
Zu HSTS besteht keine Überschneidung, auch wenn beide Header oft in einem Atemzug genannt werden. HSTS erzwingt die verschlüsselte Verbindung, die CSP regelt, was innerhalb der geladenen Seite ausgeführt wird. Die Direktive upgrade-insecure-requests stuft eingebettete HTTP-Ressourcen auf HTTPS hoch und hilft beim Mixed-Content-Problem, wirkt aber nur innerhalb einer Seite und ist kein Ersatz für HSTS. Im Sinne des Least-Privilege-Prinzips ist eine CSP die Übertragung desselben Gedankens auf den Client: Nur die Quellen, die eine Seite tatsächlich braucht, bekommen Rechte.
Die Entwicklung geht in Richtung Nonces und Trusted Types. Trusted Types, über die CSP-Direktive require-trusted-types-for 'script' aktiviert, verlangen, dass gefährliche DOM-Senken wie innerHTML nur noch typisierte Objekte statt roher Strings annehmen. Damit wird auch DOM-basiertes XSS adressiert, das eine klassische CSP nicht vollständig abdeckt. Browser-Support und Framework-Unterstützung wachsen, in Shop-Frontends ist das aber noch die Ausnahme.
Häufige Fragen
Kann ich einfach die strenge OWASP-CSP übernehmen?
Für eine Anwendung ohne Drittanbieter-Skripte ja. In einem Shop stoppt default-src 'self' ohne Payment-, Tracking- und Font-Hosts den Checkout. Der Weg führt über Report-Only, Hostliste und, wenn es sauber werden soll, über Nonces in den Templates.
Was ist der Unterschied zwischen report-uri und report-to?
report-uri ist die ältere, in der Spezifikation als veraltet markierte Direktive, die aber alle Browser verstehen. report-to nutzt die Reporting API und braucht einen zusätzlichen Reporting-Endpoints-Header. Beide parallel zu setzen ist derzeit die sicherste Variante.
Warum sehe ich CSP-Verstöße von Browser-Erweiterungen?
Erweiterungen injizieren eigene Skripte in Seiten, und manche Browser melden das als Verstoß. Diese Reports tragen in der Regel Quellen wie chrome-extension:// oder moz-extension:// und lassen sich am Report-Endpunkt filtern.
Setzt Shopware eine CSP für den Storefront?
Nein. Das Storefront-Template in shopware.security.csp_templates ist leer; nur die Administration bekommt eine Nonce-basierte Policy. Eine Storefront-CSP wird am Webserver gesetzt und muss die Inline-Skripte des Themes berücksichtigen.