Ein kleiner Script-Injection-Bug kann reichen, damit fremder Code im Browser läuft: ein gestohlener Session-Cookie, ein manipuliertes Formular, ein unsichtbarer Redirect. Viele Teams investieren deshalb in Input-Validierung und Output-Escaping – und trotzdem rutschen XSS-Lücken durch. Genau hier hilft eine Content Security Policy (CSP): Sie legt im HTTP-Header fest, welche Quellen für Skripte, Styles, Bilder oder Frames erlaubt sind. Selbst wenn ein Angreifer es schafft, HTML oder JavaScript einzuschleusen, blockt der Browser vieles automatisch.
Der Einstieg wirkt oft sperrig, weil CSP schnell „zu streng“ wird und plötzlich Ressourcen fehlen. Mit einem schrittweisen Vorgehen lässt sich das gut lösen: erst beobachten, dann gezielt erlauben, dann härter machen. Dieser Artikel zeigt den Weg – ohne Magie, dafür mit klaren Leitplanken.
Was eine CSP im Browser wirklich macht
Grundidee: Erlaubnislisten statt Bauchgefühl
Bei CSP geht es um eine einfache Frage: „Von wo darf dieser Browser Inhalte laden und ausführen?“ Anstatt dass jede Seite alles laden darf, entsteht eine Erlaubnisliste pro Ressourcentyp (z. B. Skripte oder Bilder). Der Browser prüft diese Regeln bei jedem Request. Passt die Quelle nicht zur Policy, wird blockiert.
Wichtig: CSP ist kein Ersatz für sauberes Escaping oder Validierung. Es ist eine zusätzliche Schutzschicht, die Schäden begrenzen soll, wenn doch einmal etwas durchrutscht.
Warum CSP gegen XSS so wirksam ist
Cross-Site Scripting (XSS) funktioniert oft so: Ein Angreifer bringt die Anwendung dazu, eigenen JavaScript-Code in die Seite zu schreiben (z. B. über ein unescaped Kommentar-Feld). Ohne CSP läuft dieser Code einfach. Mit CSP kann der Browser inline-Skripte blockieren und das Nachladen von Skripten aus unbekannten Quellen verhindern.
Das schützt besonders gut vor typischen XSS-Folgen wie dem Abgreifen von Tokens, dem Umleiten auf Fake-Login-Seiten oder dem stillen Ausführen von API-Aufrufen im Namen des eingeloggten Users.
Die wichtigsten Direktiven: klein starten, gezielt erweitern
default-src als Basis und warum sie nicht alles lösen sollte
Viele Policies beginnen mit default-src. Diese Direktive ist der „Fallback“, wenn für einen Ressourcentyp keine spezifische Regel gesetzt ist. Ein häufiger Startpunkt ist, nur die eigene Origin zu erlauben. In CSP-Syntax ist das meist default-src ’self‘.
In der Praxis ist es sinnvoll, danach schnell spezifisch zu werden, damit Regeln verständlich bleiben. Beispielsweise kann eine Anwendung Bilder von einem CDN laden, Skripte aber nicht. Dann ist eine eigene Regel für Images klarer als alles über default-src zu steuern.
script-src: der Kern für XSS-Schutz
Die Direktive script-src ist meist der wichtigste Teil. Hier entscheidet sich, ob JavaScript nur von der eigenen Domain geladen werden darf, ob ein bestimmtes CDN erlaubt ist oder ob inline-Skripte laufen dürfen. Gerade inline-Skripte sind ein häufiger XSS-Vektor.
Wenn eine Seite aktuell viele inline-Skripte nutzt (z. B. im Template), lässt sich CSP trotzdem einführen – aber nicht, indem man inline pauschal erlaubt. Besser ist ein kontrollierter Umbau oder die Nutzung von Nonces (dazu weiter unten).
style-src, img-src, font-src, connect-src: typische Praxis-Fälle
In modernen Webapps kommen Ressourcen aus mehreren Richtungen:
- style-src: Stylesheets aus eigener Origin oder einem CDN
- img-src: Bilder, oft auch data:-URLs (z. B. Inline-Icons), je nach Projekt
- font-src: Webfonts, häufig CDN oder eigene Domain
- connect-src: Fetch/XHR/WebSocket-Verbindungen, z. B. API-Host oder Analytics-Endpunkt
Gerade connect-src wird oft vergessen: Wenn nach Einführung von CSP plötzlich API-Calls fehlschlagen, ist das eine der ersten Stellen zum Prüfen.
Nonces und Hashes: so werden inline-Skripte kontrollierbar
Nonce-Prinzip einfach erklärt
Ein Nonce (einmaliger Zufallswert pro Request) erlaubt einzelne inline-Skripte gezielt, ohne inline pauschal zu öffnen. Der Server erzeugt für jede Antwort einen neuen Wert, setzt ihn in die CSP und schreibt ihn an die erlaubten Script-Tags. Alles andere inline bleibt blockiert.
Das ist besonders hilfreich bei SSR (Server Side Rendering) oder Templates, die kleine Inline-Snippets brauchen.
Wann Hashes besser passen
Hashes sind sinnvoll, wenn ein inline-Skript wirklich statisch ist. Dann kann der Hash des Skript-Inhalts in die Policy aufgenommen werden. Ändert sich das Skript, muss der Hash aktualisiert werden. Für häufig wechselnden Inline-Code ist das unpraktisch, für kleine stabile Snippets kann es gut funktionieren.
Einführung in der Praxis: erst beobachten, dann blocken
Schrittfolge für einen sicheren Rollout
- Policy zuerst im Report-Modus einführen (beobachten statt blocken), um echte Verstöße zu sehen.
- Nur das erlauben, was tatsächlich gebraucht wird: eigene Origin, notwendige CDNs, API-Hosts.
- Inline-Skripte identifizieren und Strategie wählen: auslagern, Nonce oder (selten) Hash.
- Report-Ausgaben regelmäßig auswerten und Regeln nachschärfen.
- Erst wenn die Seite stabil ist: Report-Modus entfernen und Blocking aktivieren.
Parallel lohnt es sich, XSS-Baustellen im Code anzugehen. Für Backend-APIs und Eingaben passt thematisch auch Input-Validierung im Backend, weil CSP am Ende nicht verhindert, dass unsaubere Daten ins System kommen.
Debugging: wo CSP-Verstöße sichtbar werden
Im Alltag passiert die Fehlersuche meist im Browser: Die Developer Tools zeigen geblockte Ressourcen und den Grund. Typisch sind geblockte Skripte von Dritt-Domains oder Inline-Snippets, die bisher „einfach so“ liefen.
Hilfreich ist, systematisch vorzugehen: Zuerst prüfen, welche Direktive zugeschlagen hat (script-src, img-src etc.), dann entscheiden, ob die Quelle legitim ist. Wenn ja: gezielt erlauben. Wenn nein: Ursache entfernen.
Häufige Stolperfallen und wie sie sich vermeiden lassen
Zu breite Freigaben machen die Policy wertlos
Ein häufiger Reflex ist, bei Problemen „schnell“ großzügig zu erlauben. Wenn am Ende überall Wildcards stehen oder inline generell erlaubt ist, sinkt der Schutz deutlich. Besser ist, die Ursachen zu finden: Woher kommt das Skript? Warum ist es inline? Kann es in eine Datei ausgelagert werden?
Third-Party-Skripte: prüfen, reduzieren, einkapseln
Analytics, Chat-Widgets oder A/B-Testing laden oft dynamisch nach. Das kann CSP kompliziert machen, weil solche Tools weitere Domains nachziehen. Hier hilft eine klare Entscheidung: Wird das Tool wirklich gebraucht? Wenn ja, sollte die Liste erlaubter Hosts bewusst und klein bleiben.
Wenn viele externe Skripte nötig sind, ist es umso wichtiger, dass der Rest der Policy sauber bleibt, statt alles pauschal zu öffnen.
Single-Page-Apps und APIs: connect-src nicht vergessen
SPAs sprechen häufig mehrere Endpunkte an (API, Auth, ggf. WebSockets). Wenn diese nicht in connect-src erlaubt sind, wirkt es wie ein Netzwerkproblem, ist aber ein CSP-Block. Wer parallel an robusten Requests arbeitet, kann auch API-Timeouts sinnvoll setzen lesen – CSP und Timeout-Probleme sehen im Fehlerbild manchmal ähnlich aus (Request bricht ab), haben aber andere Ursachen.
Kurzes Fallbeispiel aus der Webentwicklung
Vom „läuft lokal“ zum stabilen Setup
Eine typische Situation: Eine Webapp nutzt ein CDN für Fonts, ein anderes für ein UI-Framework, lädt Bilder aus einem Media-Subdomain und spricht eine API unter api.example.tld an. Zusätzlich gibt es ein Inline-Skript im Template, das ein paar Konfigurationswerte setzt.
Beim ersten CSP-Versuch mit einer sehr restriktiven Basis (nur eigene Origin) funktioniert plötzlich nichts: Fonts fehlen, Styles brechen, API-Calls schlagen fehl, und das Inline-Skript wird blockiert. Der richtige Weg ist dann nicht „alles erlauben“, sondern zu sortieren:
- Fonts: font-src um die konkrete Font-Domain ergänzen.
- Styles: style-src um das UI-CDN ergänzen (und prüfen, ob Inline-Styles genutzt werden).
- API: connect-src um api.example.tld ergänzen.
- Inline-Konfiguration: Nonce einführen, damit nur dieses Script-Tag erlaubt ist.
Damit bleibt die Policy eng genug, um XSS zu bremsen, aber flexibel genug, um reale Architektur abzubilden.
Zum Schluss: kleine Entscheidungshilfe für den Einstieg
Welche Variante passt zum aktuellen Code?
- Wenn viele Inline-Skripte im Template stehen:
- kurzfristig: Nonce-Ansatz planen
- mittelfristig: Skripte in Dateien auslagern
- Wenn fast alles aus externen Tools besteht:
- zuerst Tool-Landschaft reduzieren
- dann CSP mit kleiner, bewusst gepflegter Host-Liste
- Wenn die App hauptsächlich eigene Assets nutzt:
- mit ’self‘ starten, Report-Modus nutzen, dann schrittweise härten
Wer ohnehin an genereller Web-Sicherheit arbeitet, kann als Ergänzung HTTP Security Headers lesen, weil CSP am besten im Zusammenspiel mit weiteren Headern wirkt.

