{"id":205,"date":"2026-06-18T16:21:17","date_gmt":"2026-06-18T16:21:17","guid":{"rendered":"https:\/\/shattered.io\/de\/2026\/06\/18\/content-security-policy-nodejs\/"},"modified":"2026-06-18T16:21:17","modified_gmt":"2026-06-18T16:21:17","slug":"content-security-policy-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/de\/2026\/06\/18\/content-security-policy-nodejs\/","title":{"rendered":"Content Security Policy in Node.js: 12 Schritte [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Cross-Site-Scripting (XSS) steht seit Jahren auf der OWASP-Top-10-Liste der gef\u00e4hrlichsten Webanwendungsangriffe. Bei einem XSS-Angriff injiziert ein Angreifer schadhaften JavaScript-Code in deine Webseite, der im Browser des Opfers ausgef\u00fchrt wird, um Sitzungs-Tokens zu stehlen oder Aktionen im Namen des Nutzers durchzuf\u00fchren. Der <strong>Content Security Policy<\/strong>-Header (CSP) ist die wirksamste Browser-seitige Gegenwehr: Er teilt dem Browser exakt mit, welche Ressourcen geladen werden d\u00fcrfen, und blockiert alles andere. Dieses Tutorial zeigt, wie du CSP in 12 Schritten in einer Node.js\/Express-Anwendung implementierst, von der ersten Direktive bis zur produktionsreifen Konfiguration mit Nonces, Hashes, strict-dynamic und Violation Reporting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"was-ist-content-security-policy-und-warum-brauchst-du-sie\">Was ist Content Security Policy und warum brauchst du sie?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der <code>Content-Security-Policy<\/code>-Header ist eine HTTP-Response-Header-Richtlinie, die dem Browser mitteilt, welche Quellen er f\u00fcr Skripte, Stylesheets, Bilder, Fonts und andere Ressourcen als vertrauensw\u00fcrdig akzeptieren soll. Alles, was nicht explizit erlaubt ist, wird blockiert und ein Violation-Report kann an einen konfigurierten Endpunkt gesendet werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ohne CSP kann ein Angreifer, der es schafft, schadhaften HTML-Code in deine Seite einzuschleusen (z. B. durch eine unzureichend escapte Nutzereingabe), beliebigen JavaScript-Code ausf\u00fchren. Mit einer sorgf\u00e4ltig konfigurierten CSP ist dieser Angriff in der Regel nicht mehr m\u00f6glich, weil der Browser den injizierten Code verweigert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>CSP Level 2<\/strong> ist der produktive Baseline, den alle modernen Browser vollst\u00e4ndig unterst\u00fctzen. <strong>CSP Level 3<\/strong> (aktuelle W3C-Spezifikation) bringt Erweiterungen wie <code>'strict-dynamic'<\/code> und <code>require-trusted-types-for<\/code>, die in Chromium-Browsern bereits implementiert sind. F\u00fcr eine DACH-Nutzerbasis, bei der mehr als 65 % Chrome oder Edge nutzen, sind Level-3-Features 2026 produktionsreif.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wichtig: CSP ist kein Ersatz f\u00fcr korrektes Input-Escaping oder Parameterized Queries. Es ist eine zus\u00e4tzliche Verteidigungsschicht, die Angriffe blockiert, die trotz anderer Ma\u00dfnahmen durchkommen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"voraussetzungen\">Voraussetzungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr dieses Tutorial ben\u00f6tigst du folgende Softwareversionen:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Software<\/th><th>Mindestversion<\/th><th>Empfohlene Version<\/th><th>Zweck<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.x LTS<\/td><td>20.20.2 LTS<\/td><td>JavaScript-Laufzeitumgebung<\/td><\/tr><tr><td>npm<\/td><td>9.x<\/td><td>10.x<\/td><td>Paketverwaltung<\/td><\/tr><tr><td>Express<\/td><td>4.x<\/td><td>5.2.1<\/td><td>Web-Framework<\/td><\/tr><tr><td>helmet<\/td><td>7.x<\/td><td>8.2.0<\/td><td>HTTP-Security-Header-Middleware<\/td><\/tr><tr><td>curl oder Browser DevTools<\/td><td>beliebig<\/td><td>aktuelle Version<\/td><td>CSP-Header testen<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Au\u00dferdem brauchst du Grundkenntnisse in Node.js und Express sowie ein Terminal (Linux\/macOS) oder die PowerShell\/Git Bash unter Windows. Das fertige Projekt findest du am Ende dieses Tutorials als vollst\u00e4ndig lauff\u00e4higen Code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-1-projektstruktur-anlegen\">Schritt 1: Projektstruktur anlegen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lege ein frisches Verzeichnis an und initialisiere ein npm-Projekt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir csp-demo && cd csp-demo\nnpm init -y\nnpm install express@5 helmet@8<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Verzeichnisstruktur nach diesem Schritt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>csp-demo\/\n\u251c\u2500\u2500 node_modules\/\n\u251c\u2500\u2500 package.json\n\u2514\u2500\u2500 server.js        (noch zu erstellen)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lege die Hauptdatei <code>server.js<\/code> an:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst app = express();\nconst PORT = 3000;\n\napp.get('\/', (req, res) => {\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"de\"&gt;\n      &lt;head&gt;&lt;title&gt;CSP Demo&lt;\/title&gt;&lt;\/head&gt;\n      &lt;body&gt;\n        &lt;h1&gt;CSP Demo&lt;\/h1&gt;\n        &lt;script&gt;console.log('inline script');&lt;\/script&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\napp.listen(PORT, () => console.log(`Server l\u00e4uft auf Port ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Starte den Server mit <code>node server.js<\/code> und rufe <code>http:\/\/localhost:3000<\/code> auf. Im Browser-Netzwerktab siehst du noch keinen CSP-Header. Das \u00e4ndern wir in den n\u00e4chsten Schritten.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-2-ersten-csp-header-manuell-setzen\">Schritt 2: Ersten CSP-Header manuell setzen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor wir Helmet.js einsetzen, ist es wichtig zu verstehen, wie ein CSP-Header manuell gesetzt wird. Das gibt dir die volle Kontrolle und hilft beim Debuggen sp\u00e4terer Probleme.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst app = express();\nconst PORT = 3000;\n\n\/\/ Manuelle CSP-Middleware\napp.use((req, res, next) => {\n  res.setHeader(\n    'Content-Security-Policy',\n    [\n      \"default-src 'self'\",\n      \"script-src 'self'\",\n      \"style-src 'self'\",\n      \"img-src 'self' data:\",\n      \"connect-src 'self'\",\n      \"font-src 'self'\",\n      \"object-src 'none'\",\n      \"base-uri 'self'\",\n      \"frame-ancestors 'none'\",\n      \"form-action 'self'\"\n    ].join('; ')\n  );\n  next();\n});\n\napp.get('\/', (req, res) => {\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"de\"&gt;\n      &lt;head&gt;&lt;title&gt;CSP Demo&lt;\/title&gt;&lt;\/head&gt;\n      &lt;body&gt;\n        &lt;h1&gt;CSP aktiv&lt;\/h1&gt;\n        &lt;!-- Dieser inline-Script wird von CSP blockiert --&gt;\n        &lt;script&gt;console.log('dieser Code wird blockiert');&lt;\/script&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\napp.listen(PORT, () => console.log(`Server l\u00e4uft auf Port ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nach dem Neustart des Servers \u00f6ffne die Browser-Konsole. Du siehst jetzt eine CSP-Fehlermeldung:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Refused to execute inline script because it violates the following Content Security Policy directive:\n\"script-src 'self'\". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das ist genau das gew\u00fcnschte Verhalten. Der Browser verweigert den inline-Script, weil unsere Policy nur Skripte von <code>'self'<\/code> (der gleichen Origin) erlaubt. Eine L\u00f6sung ist, externe Skript-Dateien statt inline-Code zu verwenden oder Nonces\/Hashes einzusetzen, was wir in Schritten 7 und 8 zeigen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-3-helmet-js-csp-middleware-konfigurieren\">Schritt 3: Helmet.js CSP-Middleware konfigurieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet.js ist eine Express-Middleware-Sammlung, die zahlreiche HTTP-Security-Header auf einmal setzt. Die aktuelle Version 8.2.0 verwendet f\u00fcr CSP die Methode <code>helmet.contentSecurityPolicy()<\/code>. Die <a href=\"https:\/\/helmetjs.github.io\/\" target=\"_blank\" rel=\"noopener noreferrer\">offizielle Helmet-Dokumentation<\/a> empfiehlt, Helmet fr\u00fch in der Middleware-Kette zu platzieren:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\nconst app = express();\nconst PORT = 3000;\n\napp.use(\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\"],\n      styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n      imgSrc: [\"'self'\", \"data:\"],\n      connectSrc: [\"'self'\"],\n      fontSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      mediaSrc: [\"'self'\"],\n      frameSrc: [\"'none'\"],\n      baseUri: [\"'self'\"],\n      frameAncestors: [\"'none'\"],\n      formAction: [\"'self'\"]\n    }\n  })\n);\n\napp.get('\/', (req, res) => {\n  res.send('&lt;h1&gt;Gesichert mit Helmet CSP&lt;\/h1&gt;');\n});\n\napp.listen(PORT, () => console.log(`Port ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hinweis: In diesem Beispiel ist <code>'unsafe-inline'<\/code> f\u00fcr <code>styleSrc<\/code> gesetzt. Das ist f\u00fcr viele Projekte ein pragmatischer Einstieg, da inline-Styles sehr h\u00e4ufig in Bibliotheken vorkommen. F\u00fcr maximale Sicherheit solltest du auch hier auf Nonces oder Hashes umstellen. F\u00fcr <code>scriptSrc<\/code> ist <code>'unsafe-inline'<\/code> hingegen ein kritisches Sicherheitsrisiko und sollte nie in der Produktion stehen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u00dcberpr\u00fcfe den gesetzten Header mit curl:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -I http:\/\/localhost:3000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Ausgabe sollte einen vollst\u00e4ndigen CSP-Header enthalten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>content-security-policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; media-src 'self'; frame-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-4-csp-direktiven-im-ueberblick\">Schritt 4: CSP-Direktiven im \u00dcberblick<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CSP kennt \u00fcber 20 Direktiven. Die folgende Tabelle zeigt die wichtigsten, ihren Zweck und den empfohlenen Wert f\u00fcr eine restriktive Produktionskonfiguration. Die vollst\u00e4ndige Referenz findest du auf <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CSP\" target=\"_blank\" rel=\"noopener noreferrer\">MDN Web Docs<\/a> und auf <a href=\"https:\/\/content-security-policy.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">content-security-policy.com<\/a>:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Direktive<\/th><th>Kontrolliert<\/th><th>Empfohlener Produktionswert<\/th><th>Risiko bei Fehler<\/th><\/tr><\/thead><tbody><tr><td><code>default-src<\/code><\/td><td>Fallback f\u00fcr alle Ressourcentypen<\/td><td><code>'self'<\/code><\/td><td>Alle nicht explizit definierten Typen unkontrolliert<\/td><\/tr><tr><td><code>script-src<\/code><\/td><td>JavaScript-Quellen, inline-Scripts, eval()<\/td><td><code>'self' 'nonce-XYZ'<\/code><\/td><td>XSS \u00fcber injizierte Scripts m\u00f6glich<\/td><\/tr><tr><td><code>style-src<\/code><\/td><td>CSS-Quellen und inline-Styles<\/td><td><code>'self' 'nonce-XYZ'<\/code><\/td><td>CSS-Injection, Datenleck \u00fcber Attribute<\/td><\/tr><tr><td><code>img-src<\/code><\/td><td>Bildquellen inkl. data: URIs<\/td><td><code>'self' data: https:<\/code><\/td><td>Tracking-Pixel von Drittanbietern<\/td><\/tr><tr><td><code>connect-src<\/code><\/td><td>fetch(), XHR, WebSocket, EventSource<\/td><td><code>'self'<\/code><\/td><td>Datenleck an externe Server<\/td><\/tr><tr><td><code>font-src<\/code><\/td><td>Web-Fonts<\/td><td><code>'self'<\/code><\/td><td>Font-Fingerprinting<\/td><\/tr><tr><td><code>object-src<\/code><\/td><td>Flash, Plugins, &lt;object&gt;<\/td><td><code>'none'<\/code><\/td><td>Plugin-basierte Exploits<\/td><\/tr><tr><td><code>frame-ancestors<\/code><\/td><td>Wer darf die Seite einbetten?<\/td><td><code>'none'<\/code> oder <code>'self'<\/code><\/td><td>Clickjacking-Angriffe<\/td><\/tr><tr><td><code>base-uri<\/code><\/td><td>Erlaubte &lt;base&gt;-Tag-Ziele<\/td><td><code>'self'<\/code><\/td><td>Base-Tag-Injection rewrite relative URLs<\/td><\/tr><tr><td><code>form-action<\/code><\/td><td>Wohin Formulare submitten d\u00fcrfen<\/td><td><code>'self'<\/code><\/td><td>Formular-Hijacking an externe URL<\/td><\/tr><tr><td><code>upgrade-insecure-requests<\/code><\/td><td>HTTP zu HTTPS upgraden<\/td><td>Immer setzen<\/td><td>Mixed-Content-Warnungen<\/td><\/tr><tr><td><code>report-uri<\/code> \/ <code>report-to<\/code><\/td><td>Violation-Reporting-Endpunkt<\/td><td>Eigener Endpunkt<\/td><td>Keine Sichtbarkeit bei Verst\u00f6\u00dfen<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Eine wichtige Regel: <code>object-src 'none'<\/code> immer setzen. Flash und veraltete Browser-Plugins sind bekannte Angriffsfl\u00e4chen, die du vollst\u00e4ndig ausschlie\u00dfen solltest. Ebenso wichtig ist <code>base-uri 'self'<\/code>: Ohne diese Direktive kann ein Angreifer ein <code>&lt;base href=\"https:\/\/evil.com\"&gt;<\/code>-Tag injizieren und damit alle relativen URLs auf seine Domain umleiten.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-5-bis-6-nonce-basierte-csp-implementieren\">Schritt 5 bis 6: Nonce-basierte CSP implementieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Inline-Scripts und Inline-Styles sind im Webentwicklungsalltag unvermeidlich. Templates-Engines rendern oft dynamische Inhalte als <code>&lt;script&gt;<\/code>-Bl\u00f6cke direkt in der HTML-Antwort. Das naheliegendste L\u00f6sungskonzept, <code>'unsafe-inline'<\/code> zu erlauben, deaktiviert den XSS-Schutz komplett und ist f\u00fcr <code>script-src<\/code> inakzeptabel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die saubere L\u00f6sung ist ein <strong>Nonce<\/strong>: ein kryptografisch zuf\u00e4lliger Wert, der pro HTTP-Anfrage neu generiert wird und sowohl im CSP-Header als auch im <code>nonce<\/code>-Attribut des <code>&lt;script&gt;<\/code>-Tags erscheint. Der Browser erlaubt nur Scripts, deren Nonce mit dem Header \u00fcbereinstimmt. Ein Angreifer kann den Nonce nicht kennen, da er bei jeder Anfrage neu erstellt wird.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die Implementierung in Express nutzt das <code>crypto<\/code>-Modul aus der Node.js-Standardbibliothek:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\nconst crypto = require('crypto');\nconst app = express();\nconst PORT = 3000;\n\n\/\/ Nonce-Middleware: pro Request neu erzeugen\napp.use((req, res, next) => {\n  \/\/ 16 Bytes = 128 Bit Entropie, ausreichend f\u00fcr einen Nonce\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ Helmet mit dynamischem Nonce\napp.use((req, res, next) => {\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\", `'nonce-${res.locals.nonce}'`],\n      styleSrc: [\"'self'\", `'nonce-${res.locals.nonce}'`],\n      imgSrc: [\"'self'\", \"data:\"],\n      connectSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      baseUri: [\"'self'\"],\n      frameAncestors: [\"'none'\"],\n      formAction: [\"'self'\"],\n      upgradeInsecureRequests: []\n    }\n  })(req, res, next);\n});\n\napp.get('\/', (req, res) => {\n  const nonce = res.locals.nonce;\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"de\"&gt;\n    &lt;head&gt;\n      &lt;title&gt;CSP Nonce Demo&lt;\/title&gt;\n      &lt;!-- Nonce im style-Tag erlaubt inline-CSS --&gt;\n      &lt;style nonce=\"${nonce}\"&gt;\n        body { font-family: sans-serif; padding: 2rem; }\n        h1 { color: #2563eb; }\n      &lt;\/style&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n      &lt;h1&gt;Nonce-gesicherter Inline-Code&lt;\/h1&gt;\n      &lt;!-- Nonce im script-Tag erlaubt inline-JavaScript --&gt;\n      &lt;script nonce=\"${nonce}\"&gt;\n        console.log('Dieser inline-Script ist durch den Nonce erlaubt.');\n        document.querySelector('h1').textContent = 'Nonce funktioniert!';\n      &lt;\/script&gt;\n    &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\napp.listen(PORT, () => console.log(`Nonce-Demo l\u00e4uft auf Port ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lade die Seite zweimal neu und inspiziere den CSP-Header: Der Nonce-Wert \u00e4ndert sich bei jeder Anfrage. \u00d6ffne die Browser-Konsole und versuche, einen Script ohne Nonce auszuf\u00fchren. Der Browser verweigert ihn. Das ist der Nonce-basierte Schutz in Aktion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kritischer Hinweis:<\/strong> Der Nonce muss wirklich zuf\u00e4llig und per Request frisch sein. Verwende niemals einen statischen Nonce. Ein Angreifer, der den Nonce einmalig aussp\u00e4ht (z. B. aus einem gecachten Response), kann ihn wiederverwenden. <code>crypto.randomBytes(16)<\/code> erzeugt ausreichende Entropie (128 Bit), da die Wahrscheinlichkeit einer Kollision in der Praxis null ist.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-7-hash-basierte-csp-fuer-statische-inline-scripts\">Schritt 7: Hash-basierte CSP f\u00fcr statische Inline-Scripts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn der Inhalt eines <code>&lt;script&gt;<\/code>-Blocks sich nie \u00e4ndert, ist ein Hash eine Alternative zum Nonce. Der Browser berechnet den SHA-256-Hash des Script-Inhalts und vergleicht ihn mit dem im CSP-Header angegebenen Wert. Stimmen sie \u00fcberein, wird das Script ausgef\u00fchrt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So erzeugst du den SHA-256-Hash eines inline-Scripts in Node.js:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const crypto = require('crypto');\n\n\/\/ Exakter Script-Inhalt (Whitespace inklusive)\nconst scriptContent = `console.log('Hallo Welt');`;\n\nconst hash = crypto\n  .createHash('sha256')\n  .update(scriptContent)\n  .digest('base64');\n\nconsole.log(`'sha256-${hash}'`);\n\/\/ Ausgabe: 'sha256-abc123...'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Den erzeugten Hash tr\u00e4gst du in den <code>scriptSrc<\/code>-Direktive ein:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const SCRIPT_HASH = `'sha256-${hash}'`;\n\napp.use(\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\", SCRIPT_HASH],\n      objectSrc: [\"'none'\"],\n      baseUri: [\"'self'\"]\n    }\n  })\n);\n\napp.get('\/', (req, res) => {\n  res.send(`\n    &lt;html&gt;\n      &lt;body&gt;\n        &lt;!-- Nur dieser exakte Script-Inhalt ist erlaubt --&gt;\n        &lt;script&gt;console.log('Hallo Welt');&lt;\/script&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Vorteil von Hashes: Kein dynamisch generierter Wert n\u00f6tig, der Header kann gecacht werden. Der Nachteil: Sobald du auch nur ein Leerzeichen im Script-Inhalt \u00e4nderst, \u00e4ndert sich der Hash und du musst den Header aktualisieren. Hashes eignen sich daher f\u00fcr wirklich statische Scripts (z. B. ein Analytics-Snippet, das sich selten \u00e4ndert). CSP unterst\u00fctzt auch SHA-384 und SHA-512 f\u00fcr noch st\u00e4rkere Hashes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-8-report-only-modus-und-violation-reporting-einrichten\">Schritt 8: Report-Only Modus und Violation Reporting einrichten<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor du eine strenge CSP in der Produktion aktivierst, empfiehlt das OWASP CSP Cheat Sheet ausdr\u00fccklich, zun\u00e4chst den <strong>Report-Only Modus<\/strong> zu nutzen. Der Header <code>Content-Security-Policy-Report-Only<\/code> verh\u00e4lt sich wie ein normaler CSP-Header, blockiert aber keine Ressourcen, sondern sendet nur Verletzungsberichte. So siehst du, was eine k\u00fcnftige Policy brechen w\u00fcrde, ohne die Produktionsanwendung zu st\u00f6ren.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst app = express();\nconst PORT = 3000;\nconst violations = [];\n\n\/\/ Report-Only Middleware\napp.use((req, res, next) => {\n  res.setHeader(\n    'Content-Security-Policy-Report-Only',\n    [\n      \"default-src 'self'\",\n      \"script-src 'self'\",\n      \"style-src 'self'\",\n      \"object-src 'none'\",\n      \"base-uri 'self'\",\n      \"report-uri \/csp-report\"\n    ].join('; ')\n  );\n  next();\n});\n\n\/\/ CSP Violation Report Endpunkt\napp.use(express.json({ type: 'application\/csp-report' }));\napp.post('\/csp-report', (req, res) => {\n  const report = req.body['csp-report'] || req.body;\n  console.log('CSP-Versto\u00df:', JSON.stringify(report, null, 2));\n  violations.push({\n    time: new Date().toISOString(),\n    ...report\n  });\n  res.status(204).end();\n});\n\n\/\/ Versto\u00df-Log einsehen\napp.get('\/violations', (req, res) => {\n  res.json(violations);\n});\n\napp.get('\/', (req, res) => {\n  res.send(`\n    &lt;html&gt;\n      &lt;body&gt;\n        &lt;!-- Dieser Script w\u00fcrde im enforce-Modus blockiert (kein Nonce\/Hash) --&gt;\n        &lt;script&gt;console.log('Versto\u00df wird geloggt aber nicht blockiert');&lt;\/script&gt;\n        &lt;!-- Bild von externer Domain, w\u00fcrde in production blockiert --&gt;\n        &lt;img src=\"https:\/\/via.placeholder.com\/100\" alt=\"test\"&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\napp.listen(PORT, () => console.log(`Report-Only auf Port ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ein typischer Violation-Report vom Browser sieht so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"csp-report\": {\n    \"document-uri\": \"http:\/\/localhost:3000\/\",\n    \"referrer\": \"\",\n    \"violated-directive\": \"script-src 'self'\",\n    \"effective-directive\": \"script-src\",\n    \"original-policy\": \"default-src 'self'; script-src 'self'; ...\",\n    \"blocked-uri\": \"inline\",\n    \"status-code\": 200,\n    \"script-sample\": \"console.log('Versto\u00df wird geloggt...\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Empfehlung: Setze Report-Only f\u00fcr mindestens 2 Wochen in der Produktion. Analysiere die eingehenden Reports und passe die Policy an, bis keine legitimen Ressourcen mehr blockiert werden. Erst dann wechselst du auf den Enforce-Header. Die modernere Alternative zu <code>report-uri<\/code> ist <code>report-to<\/code> in Kombination mit der Reporting-API, die einen strukturierteren Reporting-Mechanismus bietet und in aktuellen Chromium-Browsern unterst\u00fctzt wird.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-9-und-10-strict-dynamic-und-trusted-types-api\">Schritt 9 und 10: strict-dynamic und Trusted Types API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>strict-dynamic<\/strong> ist ein Schl\u00fcsselwort aus CSP Level 3, das die Verwaltung von Nonce-basierten Policies erheblich vereinfacht. Wenn ein Script durch einen Nonce oder Hash als vertrauensw\u00fcrdig markiert ist und dieses Script seinerseits weitere Scripts dynamisch l\u00e4dt (<code>document.createElement('script')<\/code>), gelten diese Kind-Scripts ebenfalls als vertrauensw\u00fcrdig, ohne dass ihre Quell-URLs explizit in der Policy stehen m\u00fcssen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Content_Security_Policy_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP CSP Cheat Sheet<\/a> empfiehlt als moderne Produktions-Policy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use((req, res, next) => {\n  const nonce = crypto.randomBytes(16).toString('base64');\n  res.locals.nonce = nonce;\n\n  res.setHeader(\n    'Content-Security-Policy',\n    [\n      \"default-src 'self'\",\n      \/\/ strict-dynamic: nonce-approved Scripts d\u00fcrfen weitere Scripts laden\n      `script-src 'nonce-${nonce}' 'strict-dynamic'`,\n      \"style-src 'self'\",\n      \"object-src 'none'\",\n      \/\/ base-uri 'none' ist restriktiver als 'self'\n      \"base-uri 'none'\",\n      \"frame-ancestors 'none'\",\n      \"form-action 'self'\",\n      \"upgrade-insecure-requests\",\n      \"report-uri \/csp-report\"\n    ].join('; ')\n  );\n  next();\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mit <code>strict-dynamic<\/code> musst du nicht mehr alle CDN-URLs deiner JavaScript-Bibliotheken in der Policy auflisten, was die Policy wartbarer macht und weniger anf\u00e4llig f\u00fcr JSONP-Byp\u00e4sse ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Trusted Types<\/strong> ist eine erg\u00e4nzende Browser-API, die DOM-XSS-Angriffe \u00fcber gef\u00e4hrliche Sinks wie <code>innerHTML<\/code>, <code>outerHTML<\/code> oder <code>document.write()<\/code> verhindert. Anstatt rohe Strings an diese APIs zu \u00fcbergeben, erfordert Trusted Types das Erstellen einer Policy, die Strings in sichere Objekte transformiert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ CSP-Header mit Trusted Types\nContent-Security-Policy:\n  require-trusted-types-for 'script';\n  trusted-types appPolicy;\n\n\/\/ Client-seitiger JavaScript-Code\nconst policy = trustedTypes.createPolicy('appPolicy', {\n  createHTML(input) {\n    \/\/ DOMPurify als Sanitizer einsetzen\n    return DOMPurify.sanitize(input);\n  }\n});\n\n\/\/ Sicherer Einsatz von innerHTML\ndocument.querySelector('#output').innerHTML =\n  policy.createHTML(userInput);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types ist in Chromium-Browsern (Chrome, Edge) verf\u00fcgbar und wird schrittweise in anderen Browsern eingef\u00fchrt. F\u00fcr Produktionsanwendungen empfiehlt sich Report-Only (<code>Content-Security-Policy-Report-Only: require-trusted-types-for 'script'<\/code>) zum Testen, bevor auf Enforce umgestellt wird.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-11-csp-fuer-single-page-applications-react-vue-angular\">Schritt 11: CSP f\u00fcr Single Page Applications (React, Vue, Angular)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SPAs stellen besondere Herausforderungen f\u00fcr CSP dar. React und Vue rendern DOM-Elemente dynamisch, und viele Build-Tools injizieren inline-Scripts in die <code>index.html<\/code>. Folgende Strategie funktioniert f\u00fcr die meisten SPA-Setups mit einem Node.js\/Express-Backend:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\nconst crypto = require('crypto');\nconst path = require('path');\nconst app = express();\n\n\/\/ Nonce f\u00fcr jede Anfrage generieren\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ CSP f\u00fcr SPA: strict-dynamic erlaubt Bundle-Loader\napp.use((req, res, next) => {\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\n        \"'self'\",\n        (req, res) => `'nonce-${res.locals.nonce}'`,\n        \"'strict-dynamic'\"\n      ],\n      styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n      imgSrc: [\"'self'\", \"data:\", \"blob:\"],\n      connectSrc: [\n        \"'self'\",\n        \"https:\/\/api.deineapp.de\",\n        \"wss:\/\/ws.deineapp.de\"\n      ],\n      fontSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      frameAncestors: [\"'none'\"],\n      baseUri: [\"'self'\"],\n      formAction: [\"'self'\"],\n      upgradeInsecureRequests: []\n    },\n    reportOnly: false\n  })(req, res, next);\n});\n\n\/\/ SPA: Index-HTML dynamisch rendern um Nonce einzusetzen\napp.get('\/', (req, res) => {\n  const nonce = res.locals.nonce;\n  \/\/ In Produktion: Template-Engine oder fs.readFile verwenden\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"de\"&gt;\n    &lt;head&gt;\n      &lt;meta charset=\"UTF-8\"&gt;\n      &lt;title&gt;React App&lt;\/title&gt;\n      &lt;link rel=\"stylesheet\" href=\"\/static\/css\/main.css\"&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n      &lt;div id=\"root\"&gt;&lt;\/div&gt;\n      &lt;!-- Nonce erm\u00f6glicht den Bundle-Loader und React hydration --&gt;\n      &lt;script nonce=\"${nonce}\" src=\"\/static\/js\/main.js\"&gt;&lt;\/script&gt;\n    &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\n\/\/ Statische Assets servieren\napp.use('\/static', express.static(path.join(__dirname, 'build\/static')));\n\napp.listen(3000);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wichtige Punkte f\u00fcr SPAs: <code>'unsafe-inline'<\/code> f\u00fcr <code>styleSrc<\/code> ist bei vielen UI-Bibliotheken (Material UI, Ant Design) vorerst n\u00f6tig, da sie Styles direkt in den DOM schreiben. F\u00fcr <code>connectSrc<\/code> musst du explizit alle API-Endpunkte und WebSocket-URLs eintragen. <code>blob:<\/code> in <code>imgSrc<\/code> ist oft n\u00f6tig, wenn Nutzer Bilder hochladen und diese \u00fcber <code>URL.createObjectURL()<\/code> angezeigt werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-12-csp-testen-debuggen-und-finalisieren\">Schritt 12: CSP testen, debuggen und finalisieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor du die CSP in der Produktion schaltest, solltest du sie gr\u00fcndlich testen. Drei Werkzeuge sind hier unverzichtbar:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Browser DevTools:<\/strong> In Chrome und Firefox gibt die Konsole detaillierte CSP-Fehlermeldungen aus, welche Direktive verletzt wurde und welche Ressource blockiert wird. \u00d6ffne DevTools mit F12, wechsle zum Reiter &#8220;Konsole&#8221; und lade die Seite neu. Alle CSP-Verst\u00f6\u00dfe erscheinen als rote Fehlermeldungen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. CSP Evaluator:<\/strong> Das Tool <a href=\"https:\/\/csp-evaluator.withgoogle.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">CSP Evaluator von Google<\/a> analysiert deine Policy auf bekannte Schwachstellen, Bypass-M\u00f6glichkeiten und Konfigurationsfehler. F\u00fcge deinen CSP-Header-Wert ein und erhalte eine detaillierte Bewertung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Automatisierter Test in Node.js:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const http = require('http');\n\nfunction testCSP(port = 3000) {\n  return new Promise((resolve, reject) => {\n    http.get(`http:\/\/localhost:${port}\/`, (res) => {\n      const csp = res.headers['content-security-policy'];\n      if (!csp) {\n        reject(new Error('Kein CSP-Header gefunden!'));\n        return;\n      }\n\n      const checks = {\n        defaultSrc: csp.includes(\"default-src 'self'\"),\n        noUnsafeInlineScript: !csp.match(\/script-src[^;]*'unsafe-inline'\/),\n        noUnsafeEval: !csp.includes(\"'unsafe-eval'\"),\n        objectSrcNone: csp.includes(\"object-src 'none'\"),\n        baseUriRestricted: csp.includes(\"base-uri\"),\n        frameAncestors: csp.includes(\"frame-ancestors\"),\n        hasNonce: csp.includes(\"nonce-\") || csp.includes(\"sha256-\")\n      };\n\n      console.log('CSP-Pr\u00fcfergebnis:');\n      Object.entries(checks).forEach(([key, value]) => {\n        console.log(`  ${value ? '\u2713' : '\u2717'} ${key}`);\n      });\n\n      const passed = Object.values(checks).filter(Boolean).length;\n      console.log(`\\n${passed}\/${Object.keys(checks).length} Checks bestanden`);\n      resolve(checks);\n    }).on('error', reject);\n  });\n}\n\ntestCSP().catch(console.error);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Skript pr\u00fcft die wichtigsten Sicherheitskriterien und gibt eine \u00fcbersichtliche Zusammenfassung aus. F\u00fchre es als Teil deiner CI\/CD-Pipeline aus, um sicherzustellen, dass die CSP-Konfiguration nach jedem Deployment korrekt ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die finale Produktionskonfiguration empfiehlt die <a href=\"https:\/\/www.w3.org\/TR\/CSP3\/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C CSP Level 3 Spezifikation<\/a> folgende Reihenfolge beim Rollout: (1) Report-Only mit einer strikten Policy f\u00fcr 2 Wochen, (2) Analyse der Violation Reports, (3) Policy anpassen bis alle legitimen Ressourcen erlaubt sind, (4) Umschalten auf Enforce-Modus, (5) Weiteres Monitoring \u00fcber den Reporting-Endpunkt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufige-fehler-bei-der-csp-implementierung\">H\u00e4ufige Fehler bei der CSP-Implementierung<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Diese f\u00fcnf Fehler f\u00fchren bei CSP-Implementierungen am h\u00e4ufigsten zu Sicherheitsl\u00fccken oder broken Deployments:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 1: <code>'unsafe-inline'<\/code> in script-src erlauben.<\/strong> Sobald <code>'unsafe-inline'<\/code> in <code>script-src<\/code> steht, ist der XSS-Schutz von CSP praktisch aufgehoben. Der Angreifer kann trotzdem beliebige inline-Scripts injizieren. Die korrekte Alternative sind Nonces oder Hashes. <code>'unsafe-inline'<\/code> wird zudem ignoriert, wenn ein Nonce oder Hash in der Policy vorhanden ist, weshalb es bei nonce-basierten Policies keinen Effekt hat, aber trotzdem nicht stehen sollte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 2: <code>'unsafe-eval'<\/code> erlauben.<\/strong> <code>eval()<\/code>, <code>Function()<\/code> und verwandte Konstrukte sind h\u00e4ufige XSS-Vektoren. Wer <code>'unsafe-eval'<\/code> in die Policy aufnimmt, \u00f6ffnet eine Hintert\u00fcr f\u00fcr Angreifer. Viele JavaScript-Bibliotheken sind ohne <code>eval()<\/code> einsetzbar. Pr\u00fcfe die Anforderungen deiner Dependencies und versuche, auf <code>eval<\/code>-freie Alternativen umzusteigen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 3: Statische Nonces wiederverwenden.<\/strong> Ein Nonce verliert seine Schutzwirkung, wenn er nicht bei jeder HTTP-Anfrage neu generiert wird. Ein Angreifer, der eine Seite mit einem gecachten Nonce in einem CDN oder Reverse Proxy sieht, kann ihn in seinen Angriffscode einf\u00fcgen. Stelle sicher, dass kein CDN oder Reverse Proxy deine HTML-Antworten mit CSP-Nonces cachet. Setze <code>Cache-Control: no-store<\/code> f\u00fcr nonce-haltige Antworten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 4: base-uri nicht einschr\u00e4nken.<\/strong> Ohne <code>base-uri 'self'<\/code> oder <code>base-uri 'none'<\/code> kann ein Angreifer ein <code>&lt;base href=\"https:\/\/evil.com\"&gt;<\/code>-Tag injizieren. Alle relativen URLs auf der Seite werden dann auf die Domain des Angreifers umgeleitet, was Skripte, Styles und Formulare betrifft. Dieser Angriff ist subtil und wird oft \u00fcbersehen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 5: JSONP-f\u00e4hige Domains in script-src allowlisten.<\/strong> Wenn du eine Domain in <code>script-src<\/code> eintr\u00e4gst, die JSONP-Endpunkte anbietet (z. B. \u00e4ltere Google-APIs, Social-Media-Widgets), kann ein Angreifer diese Endpunkte nutzen, um beliebigen JavaScript-Code einzuschleusen. Pr\u00fcfe bei jeder erlaubten Domain, ob sie JSONP-Callbacks unterst\u00fctzt. Die L\u00f6sung ist, stattdessen auf <code>'strict-dynamic'<\/code> mit Nonces umzusteigen, das JSONP-Byp\u00e4sse eliminiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 6: object-src und plugin-types vergessen.<\/strong> Flash-Plugins und Java-Applets sind l\u00e4ngst obsolet, werden aber in \u00e4lteren Browsern noch unterst\u00fctzt und sind bekannte Exploit-Vektoren. <code>object-src 'none'<\/code> schlie\u00dft diese Angriffsfl\u00e4che vollst\u00e4ndig.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 7: Policy zu permissiv starten und nie versch\u00e4rfen.<\/strong> Viele Teams setzen eine breite Policy ein (&#8220;erst mal alles erlauben, damit nichts bricht&#8221;) und kommen nie dazu, sie zu versch\u00e4rfen. Der richtige Ansatz ist umgekehrt: Mit einem maximalen Restrict starten, Report-Only nutzen und nur das freischalten, was wirklich gebraucht wird.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-8-haeufige-csp-probleme-und-ihre-loesungen\">Troubleshooting: 8 h\u00e4ufige CSP-Probleme und ihre L\u00f6sungen<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Problem<\/th><th>Ursache<\/th><th>L\u00f6sung<\/th><\/tr><\/thead><tbody><tr><td>Inline-Script wird blockiert trotz Nonce<\/td><td>Nonce im Header stimmt nicht mit Nonce im HTML \u00fcberein (z. B. durch Caching)<\/td><td>Cache-Control: no-store f\u00fcr HTML-Antworten setzen, Nonce-Generierung debuggen<\/td><\/tr><tr><td>Externe Schriftart (Google Fonts) blockiert<\/td><td>font-src fehlt oder erlaubt fonts.gstatic.com nicht<\/td><td>font-src &#8216;self&#8217; https:\/\/fonts.gstatic.com hinzuf\u00fcgen<\/td><\/tr><tr><td>WebSocket-Verbindung blockiert<\/td><td>wss:\/\/ in connect-src fehlt<\/td><td>connect-src &#8216;self&#8217; wss:\/\/dein-server.de hinzuf\u00fcgen<\/td><\/tr><tr><td>React\/Vue Build-Scripts blockiert<\/td><td>Gebaute Bundle-Files haben keine Nonce<\/td><td>strict-dynamic einsetzen oder Nonce via Template-Engine in index.html injizieren<\/td><\/tr><tr><td>Blob-URLs f\u00fcr Datei-Uploads blockiert<\/td><td>blob: in img-src fehlt<\/td><td>img-src &#8216;self&#8217; data: blob: erg\u00e4nzen<\/td><\/tr><tr><td>eval() in Third-Party-Library wirft CSP-Fehler<\/td><td>Bibliothek verwendet eval() oder Function()-Konstruktor<\/td><td>Bibliothek auf eval-freie Version upgraden oder ersetzen; als letztes Mittel unsafe-eval nur f\u00fcr src-Muster dieser Bibliothek<\/td><\/tr><tr><td>CSP-Header erscheint nicht in curl-Ausgabe<\/td><td>Middleware falsch platziert (nach dem ersten res.send() Aufruf)<\/td><td>Helmet\/CSP-Middleware vor allen Routen platzieren, app.use() vor app.get()<\/td><\/tr><tr><td>Bilder von S3 oder CDN werden blockiert<\/td><td>img-src erlaubt nur &#8216;self&#8217;<\/td><td>img-src &#8216;self&#8217; https:\/\/dein-bucket.s3.eu-central-1.amazonaws.com hinzuf\u00fcgen<\/td><\/tr><tr><td>report-uri erh\u00e4lt keine Berichte<\/td><td>Content-Type des Reports nicht erkannt<\/td><td>express.json({ type: &#8216;application\/csp-report&#8217; }) als Middleware hinzuf\u00fcgen<\/td><\/tr><tr><td>Policy bricht Stripe\/PayPal-Checkout<\/td><td>Payment-IFrames werden von frame-src blockiert<\/td><td>frame-src &#8216;self&#8217; https:\/\/js.stripe.com oder entsprechende Payment-CDN-URL erg\u00e4nzen<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Ein h\u00e4ufiges Debugging-Problem ist, dass der CSP-Header korrekt gesetzt ist, aber der Browser ihn ignoriert, weil eine <code>meta http-equiv=\"Content-Security-Policy\"<\/code>-Direktive im HTML-Dokument einen anderen Wert setzt. Der Header hat Priorit\u00e4t vor dem Meta-Tag, aber wenn beide vorhanden sind, k\u00f6nnen unerwartete Konflikte entstehen. Entferne alle CSP-Meta-Tags und setze die Policy ausschlie\u00dflich \u00fcber den HTTP-Header.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"fortgeschrittene-tipps-production-ready-csp\">Fortgeschrittene Tipps: Production-Ready CSP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr eine produktionsreife CSP-Implementierung gibt es einige fortgeschrittene Muster, die \u00fcber die Grundkonfiguration hinausgehen:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Differenzierte Policies f\u00fcr verschiedene Routen:<\/strong> Nicht alle Seiten einer Anwendung haben die gleichen Anforderungen. Eine Admin-Konsole braucht keine Payment-iFrames. Eine \u00f6ffentliche Produktseite braucht keine WebSocket-Verbindungen. Setze route-spezifische CSP-Header \u00fcber separate Middleware-Instanzen statt einer globalen Policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Reporting-API statt report-uri:<\/strong> Die modernere <code>report-to<\/code>-Direktive verwendet die Reporting API und unterst\u00fctzt Gruppenreporting, Sampling-Rate und strukturiertere Reports. F\u00fcr eine zukunftssichere Implementierung kombiniere beide:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Reporting-Gruppe definieren\nres.setHeader('Reporting-Endpoints', 'csp-endpoint=\"https:\/\/deine-app.de\/csp-report\"');\n\n\/\/ CSP mit report-to und report-uri (Backward-Compat.)\nres.setHeader(\n  'Content-Security-Policy',\n  [\n    \"default-src 'self'\",\n    `script-src 'nonce-${nonce}' 'strict-dynamic'`,\n    \"object-src 'none'\",\n    \"base-uri 'none'\",\n    \"report-to csp-endpoint\",\n    \"report-uri \/csp-report\"  \/\/ Fallback f\u00fcr \u00e4ltere Browser\n  ].join('; ')\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>CSP-Nonce mit Template-Engines (EJS, Pug, Handlebars):<\/strong> Template-Engines machen es einfach, den Nonce in alle Templates zu injizieren. Bei EJS \u00fcbergibst du den Nonce als Template-Variable und verwendest ihn als <code>&lt;%- nonce %&gt;<\/code>. Das Wichtigste ist, dass alle Script- und Style-Tags den Nonce erhalten, auch die vom Framework generierten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sicherheitsaudit mit dem OWASP Secure Headers Project:<\/strong> Das <a href=\"https:\/\/owasp.org\/www-project-secure-headers\/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Secure Headers Project<\/a> pflegt eine Referenzliste empfohlener Header-Konfigurationen. Dort findest du aktuelle Empfehlungen f\u00fcr CSP, HSTS, X-Frame-Options und weitere Header, die du neben CSP setzen solltest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Performance-Aspekte:<\/strong> CSP-Header haben keinen messbaren Performance-Einfluss auf die Serverseite, da es sich um einfache String-Operationen handelt. Der Browser muss jedoch die Policy parsen und bei jeder Ressourcenanforderung pr\u00fcfen. Bei sehr langen Policies (z. B. viele allowlistete Domains) kann dies marginal l\u00e4nger dauern. Eine gut strukturierte Policy mit Nonces statt langen Domain-Listen ist daher auch aus Performance-Sicht vorzuziehen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vollstaendiges-beispielprojekt-produktionsbereite-csp\">Vollst\u00e4ndiges Beispielprojekt: Produktionsbereite CSP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hier ist die vollst\u00e4ndige, produktionsbereite <code>server.js<\/code> mit allen besprochenen Features:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const express = require('express');\nconst helmet = require('helmet');\nconst crypto = require('crypto');\nconst app = express();\nconst PORT = process.env.PORT || 3000;\nconst violations = [];\n\n\/\/ 1. Nonce pro Request erzeugen\napp.use((req, res, next) => {\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\n\/\/ 2. Helmet mit vollst\u00e4ndiger CSP-Konfiguration\napp.use((req, res, next) => {\n  const nonce = res.locals.nonce;\n  helmet.contentSecurityPolicy({\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\n        \"'self'\",\n        `'nonce-${nonce}'`,\n        \"'strict-dynamic'\"\n      ],\n      styleSrc: [\"'self'\", `'nonce-${nonce}'`],\n      imgSrc: [\"'self'\", \"data:\", \"blob:\"],\n      connectSrc: [\"'self'\"],\n      fontSrc: [\"'self'\"],\n      objectSrc: [\"'none'\"],\n      mediaSrc: [\"'self'\"],\n      frameSrc: [\"'none'\"],\n      frameAncestors: [\"'none'\"],\n      baseUri: [\"'none'\"],\n      formAction: [\"'self'\"],\n      upgradeInsecureRequests: [],\n      reportUri: ['\/csp-report']\n    }\n  })(req, res, next);\n});\n\n\/\/ 3. CSP Violation Reports empfangen\napp.use(express.json({ type: ['application\/json', 'application\/csp-report'] }));\n\napp.post('\/csp-report', (req, res) => {\n  const report = req.body?.['csp-report'] || req.body;\n  if (report) {\n    const entry = {\n      time: new Date().toISOString(),\n      ip: req.ip,\n      violatedDirective: report['violated-directive'],\n      blockedUri: report['blocked-uri'],\n      documentUri: report['document-uri']\n    };\n    violations.push(entry);\n    \/\/ In Produktion: Logging-Service verwenden (z.B. Winston, Sentry)\n    console.warn('CSP-Versto\u00df:', entry);\n  }\n  res.status(204).end();\n});\n\n\/\/ 4. Hauptroute mit Nonce-Injection\napp.get('\/', (req, res) => {\n  const nonce = res.locals.nonce;\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html lang=\"de\"&gt;\n    &lt;head&gt;\n      &lt;meta charset=\"UTF-8\"&gt;\n      &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\n      &lt;title&gt;CSP Demo&lt;\/title&gt;\n      &lt;style nonce=\"${nonce}\"&gt;\n        body { font-family: system-ui, sans-serif; max-width: 800px; margin: 2rem auto; padding: 1rem; }\n        .badge { background: #22c55e; color: white; padding: .25rem .75rem; border-radius: 9999px; font-size: .875rem; }\n        pre { background: #f1f5f9; padding: 1rem; border-radius: .5rem; overflow-x: auto; }\n      &lt;\/style&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n      &lt;h1&gt;Content Security Policy &lt;span class=\"badge\"&gt;aktiv&lt;\/span&gt;&lt;\/h1&gt;\n      &lt;p&gt;Diese Seite ist durch einen Nonce-basierten CSP-Header gesichert.&lt;\/p&gt;\n      &lt;pre id=\"csp-output\"&gt;CSP wird geladen...&lt;\/pre&gt;\n      &lt;script nonce=\"${nonce}\"&gt;\n        \/\/ Dieser inline-Script ist durch den Nonce erlaubt\n        fetch('\/api\/csp-info')\n          .then(r =&gt; r.json())\n          .then(data =&gt; {\n            document.getElementById('csp-output').textContent =\n              'Nonce: ' + data.nonce.substring(0, 10) + '... (erste 10 Zeichen)\\\\n' +\n              'Violations gesamt: ' + data.violations;\n          });\n      &lt;\/script&gt;\n    &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\n\/\/ 5. API-Endpunkt f\u00fcr CSP-Status\napp.get('\/api\/csp-info', (req, res) => {\n  res.json({\n    nonce: res.locals.nonce,\n    violations: violations.length\n  });\n});\n\napp.listen(PORT, () => {\n  console.log(`Server l\u00e4uft auf Port ${PORT}`);\n  console.log(`CSP aktiv: Nonce-basiert mit strict-dynamic`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Teste die Anwendung mit <code>node server.js<\/code>, \u00f6ffne <code>http:\/\/localhost:3000<\/code> und \u00fcberpr\u00fcfe den CSP-Header in den Browser-DevTools unter Netzwerk > Headers. Du siehst den vollst\u00e4ndigen Header inklusive des frisch generierten Nonce-Werts, der bei jedem Reload wechselt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"weiterfuehrende-inhalte\">Weiterf\u00fchrende Inhalte<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Content Security Policy ist eine von mehreren Sicherheitsebenen f\u00fcr Node.js-Anwendungen. Diese Artikel bauen auf dem hier gezeigten Wissen auf oder erg\u00e4nzen es:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/de\/http-security-headers-nodejs-helmet\/\">HTTP Security Headers in Node.js: 12 Schritte mit Helmet.js<\/a> &#8211; X-Frame-Options, HSTS, X-Content-Type-Options und weitere Header<\/li>\n<li><a href=\"\/de\/csrf-protection-nodejs\/\">CSRF Protection in Node.js: 12 Steps<\/a> &#8211; Schutz vor Cross-Site Request Forgery als Erg\u00e4nzung zu CSP<\/li>\n<li><a href=\"\/de\/oauth-pkce-nodejs\/\">OAuth 2.1 mit PKCE in Node.js: 12 Schritte<\/a> &#8211; Sichere Authentifizierung f\u00fcr APIs und SPAs<\/li>\n<li><a href=\"\/de\/rate-limiting-nodejs\/\">Rate Limiting in Node.js: 12 Steps, 30 Min<\/a> &#8211; Brute-Force und DDoS-Schutz auf Anwendungsebene<\/li>\n<li><a href=\"\/de\/nodejs-session-management\/\">Node.js Session Management: 11 Steps, 30 Min<\/a> &#8211; Sichere Session-Verwaltung in Express<\/li>\n<li><a href=\"\/de\/two-factor-authentication-nodejs\/\">Two-Factor Authentication in Node.js: 11 Steps<\/a> &#8211; TOTP-basierte 2FA implementieren<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-content-security-policy-in-node-js\">FAQ: Content Security Policy in Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"macht-csp-xss-schutz-durch-input-escaping-ueberfluessig\">Macht CSP XSS-Schutz durch Input-Escaping \u00fcberfl\u00fcssig?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nein. CSP und Input-Escaping sind komplement\u00e4re Ma\u00dfnahmen. CSP ist eine Verteidigungsschicht im Browser und schl\u00e4gt fehl, wenn ein Angreifer einen Bypass findet (z. B. \u00fcber eine JSONP-f\u00e4hige Domain in der allowlist). Input-Escaping verhindert, dass sch\u00e4dlicher Code \u00fcberhaupt in die HTML-Ausgabe gelangt. Beide Ma\u00dfnahmen zusammen (Defense in Depth) sind der richtige Ansatz.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-gross-ist-der-performance-overhead-durch-csp-nonces\">Wie gro\u00df ist der Performance-Overhead durch CSP-Nonces?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>crypto.randomBytes(16)<\/code> ist eine sehr schnelle Operation auf modernen Server-CPUs und kostet weniger als 0,1 Millisekunde. Der Performance-Overhead durch CSP ist vernachl\u00e4ssigbar. Wichtiger ist, dass nonce-haltige HTML-Antworten nicht gecacht werden d\u00fcrfen (Cache-Control: no-store), was caching-Strategien beeinflusst.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kann-ich-csp-mit-einem-cdn-wie-cloudflare-kombinieren\">Kann ich CSP mit einem CDN wie Cloudflare kombinieren?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja, aber mit Vorsicht. Wenn Cloudflare oder ein anderes CDN HTML-Antworten cachet, werden gecachte Nonces an mehrere Nutzer ausgeliefert, was den Nonce-Schutz aufhebt. Die L\u00f6sung ist, HTML mit Nonces vom CDN auszunehmen (z. B. \u00fcber Cache-Control: no-store auf der Origin oder einen Cache-Bypass f\u00fcr HTML-Antworten). Statische Assets (CSS, JS) k\u00f6nnen weiterhin gecacht werden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"was-passiert-mit-browsern-die-csp-nicht-unterstuetzen\">Was passiert mit Browsern, die CSP nicht unterst\u00fctzen?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CSP ist ein Opt-in-Sicherheitsmechanismus. Browser, die CSP nicht verstehen, ignorieren den Header und rendern die Seite normal. Das Verhalten der Seite \u00e4ndert sich nicht, nur die Schutzwirkung fehlt. Da aber alle modernen Browser (Chrome ab Version 25, Firefox ab 23, Safari ab 10, Edge seit Beginn) CSP unterst\u00fctzen, ist das 2026 kein praktisches Problem f\u00fcr die \u00fcberwiegende Mehrheit der Nutzer.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-setze-ich-csp-fuer-eine-next-js-anwendung-ein\">Wie setze ich CSP f\u00fcr eine Next.js-Anwendung ein?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In Next.js setzt du CSP-Header entweder \u00fcber <code>next.config.js<\/code> (statische Policy ohne Nonces) oder \u00fcber benutzerdefinierte Server-Middleware bzw. <code>middleware.ts<\/code> f\u00fcr dynamische Nonce-basierte Policies. Bei Server-Side Rendering injizierst du den Nonce \u00fcber <code>getServerSideProps<\/code> oder die neuen App Router Server Components. Die offizielle Next.js-Dokumentation beschreibt den empfohlenen Weg f\u00fcr beide Ans\u00e4tze.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"welches-helmet-js-csp-preset-ist-der-beste-ausgangspunkt\">Welches Helmet.js CSP-Preset ist der beste Ausgangspunkt?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Helmet 8.x hat kein eingebautes &#8220;strict&#8221;-Preset f\u00fcr CSP. Der beste Ausgangspunkt ist die Konfiguration aus diesem Tutorial: <code>default-src 'self'<\/code>, nonce-basiertes <code>script-src<\/code>, <code>object-src 'none'<\/code>, <code>base-uri 'none'<\/code> und <code>frame-ancestors 'none'<\/code>. Diese 5 Direktiven decken die h\u00e4ufigsten Angriffsvektoren ab und entsprechen den aktuellen OWASP-Empfehlungen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-debug-ich-csp-verstoesse-in-der-produktion\">Wie debug ich CSP-Verst\u00f6\u00dfe in der Produktion?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Richte einen <code>\/csp-report<\/code>-Endpunkt ein wie in Schritt 8 gezeigt und leite die Reports in deinen Logging-Service (Sentry, Datadog, ELK Stack). Filtere bekannte False Positives heraus (Browser-Plugins, Passwort-Manager injizieren oft Scripts). Schalte zus\u00e4tzlich tempor\u00e4r auf <code>Content-Security-Policy-Report-Only<\/code> um, wenn du eine Policy-\u00c4nderung testest, ohne die Produktion zu unterbrechen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"reicht-csp-allein-fuer-pci-dss-oder-bsi-compliance\">Reicht CSP allein f\u00fcr PCI-DSS- oder BSI-Compliance?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CSP ist eine wichtige Ma\u00dfnahme, reicht aber nicht allein. PCI-DSS Version 4.0 (g\u00fcltig ab M\u00e4rz 2025) fordert unter Requirement 6.4.3 explizit, alle client-seitigen Scripts auf Zahlungsseiten zu kontrollieren, wobei CSP eine der akzeptierten Implementierungsmethoden ist. F\u00fcr vollst\u00e4ndige Compliance brauchst du zus\u00e4tzlich Input-Validierung, sichere HTTP-Header (HSTS, X-Frame-Options), regelm\u00e4\u00dfige Penetrationstests und ein Vulnerability Management. Das BSI Grundschutz-Kompendium empfiehlt CSP als Teil des Web-Anwendungsschutzes ohne spezifische Versionsvorgaben.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cross-Site-Scripting (XSS) steht seit Jahren auf der OWASP-Top-10-Liste der gef\u00e4hrlichsten Webanwendungsangriffe. Bei einem XSS-Angriff injiziert ein Angreifer schadhaften JavaScript-Code in deine Webseite, der im Browser des Opfers ausgef\u00fchrt wird, um\u2026<\/p>\n","protected":false},"author":4,"featured_media":206,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-205","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/205","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/comments?post=205"}],"version-history":[{"count":0,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/205\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media\/206"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media?parent=205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/categories?post=205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/tags?post=205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}