EN DE

Content Security Policy auf WordPress

WordPress ist die kniffligste der üblichen Plattformen wenn's um eine CSP geht. Nicht weil der Code schwierig wäre — es ist ein 5-Zeilen-Hook — sondern weil das WordPress-Ökosystem voll ist mit Inline-Skripten, Inline-Styles und Dritt-Anbieter-Widgets die die CSP alle whitelisten muss. Diese Anleitung deckt das minimale Deployment ab, plus die realen Kopfschmerzen: Gutenberg, Plugin-Konflikte, Admin-Bar und wo man den Code hinpackt, damit ein Theme-Update ihn nicht wegwischt.

Das minimale Deployment

WordPress bietet den send_headers-Action-Hook an, der unmittelbar vor dem Senden jedes Response feuert. Wir registrieren einen Callback der PHPs header() aufruft:

add_action('send_headers', function() {
    header("Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:");
});

Das ist alles. Die Frage ist, wo wir den Code ablegen.

Wo der Code hingehört: Must-Use-Plugin schlägt functions.php

Viele Tutorials sagen "pack das in die functions.php Ihres Themes". Bitte nicht. Warum:

Die robuste Lösung ist ein Must-Use-Plugin. Ordner anlegen falls er nicht existiert:

mkdir -p wp-content/mu-plugins

Und dann eine Datei wp-content/mu-plugins/csp.php mit:

<?php
/**
 * Plugin Name: Content Security Policy
 * Description: Setzt den CSP-Header seitenweit. Überlebt Theme-Wechsel und Updates.
 */
if (!defined('ABSPATH')) exit;

add_action('send_headers', function() {
    header("Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:");
});

Must-Use-Plugins werden automatisch geladen — kein "Aktivieren"-Schritt nötig — und sie sind außerhalb des Theme-/Plugin-Update-Zyklus. Sie bleiben also für immer bestehen, außer Sie entfernen sie explizit.

Warum WordPress fast immer 'unsafe-inline' braucht

WordPress-Core, fast jedes Theme und die meisten Plugins erzeugen überall auf der Seite Inline-<script>- und <style>-Blöcke. Eine kurze Liste der Täter:

Ohne 'unsafe-inline' in sowohl script-src als auch style-src bricht die Seite sofort. Die striktere Alternative (Nonces) erfordert ein WordPress-CSP-Plugin das den Output-Buffer filtert und jedem Inline-Tag ein Nonce-Attribut verpasst. Das ist machbar aber in freier Wildbahn selten — ein nicht-trivialer Performance-Overhead auf jeden Request.

Pragmatische Empfehlung: Starten Sie mit 'unsafe-inline', stellen Sie sicher dass der Rest Ihrer Policy sinnvoll ist (kein 'unsafe-eval', tight default-src, striktes frame-ancestors), und iterieren Sie zu strikteren Policies später wenn Sie bestimmte Inline-Skripte auditiert haben.

Abkürzung: Unser Scanner crawlt eine WordPress-Seite und erkennt, welche externen Origins geladen werden (fonts.googleapis.com, google-analytics.com, wp.com-CDNs, Cookie-Banner etc.) — plus markiert ob 'unsafe-inline' überhaupt wirklich nötig ist, basierend auf dem was Ihre Seite tatsächlich ausliefert. Das Ergebnis ist eine CSP, die direkt für Ihre exakte Theme-/Plugin-Kombination passt.

Typische Fehlerquellen

Die Admin-Bar taucht im Frontend auf und braucht eigene Origins

Wenn Sie eingeloggt sind, injiziert WordPress die Admin-Bar — die lädt inline Skripte, Styles und manchmal Gravatar-Bilder von externen Origins. Testen Sie Ihre CSP sowohl ausgeloggt ALS AUCH eingeloggt. Der eingeloggte Fall braucht typischerweise extra Freigaben für https://secure.gravatar.com in img-src.

Plugin-Konflikte bei Updates

Ein Plugin updated, fängt an ein neues externes Widget zu laden, und plötzlich blockiert Ihre CSP etwas, das gestern noch ging. Lassen Sie Ihre CSP nach größeren Plugin-Updates für eine Woche im Report-Only, oder abonnieren Sie report-uri-Benachrichtigungen damit Sie Verstöße sehen bevor Nutzer sich beschweren.

Gutenberg-Editor lädt nicht

Wenn Sie eine strikte CSP ohne 'unsafe-inline' in script-src durchsetzen, kann der Gutenberg-Block-Editor nicht mehr gerendert werden. Entweder lockern Sie die Policy für /wp-admin/ (siehe der bedingte Ansatz unten) oder behalten 'unsafe-inline' bei.

Bedingte CSP für /wp-admin/ vs. Frontend

Sie können im Frontend eine strikte CSP ausliefern und im Admin — wo die meisten Inline-Schmerzen wohnen — lockerer sein:

add_action('send_headers', function() {
    if (is_admin()) {
        header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:");
    } else {
        header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:");
    }
});

Zu beachten: send_headers feuert nicht auf jedem Admin-Request-Pfad. Für maximale Abdeckung setzen Sie die Admin-CSP stattdessen in admin_init:

add_action('admin_init', function() {
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:");
});

Caching-Plugins entfernen Security-Header

Manche Caching-Plugins (WP Rocket, W3 Total Cache, LiteSpeed Cache) liefern statische HTML-Dateien aus und umgehen PHP komplett — das bedeutet Ihr send_headers-Hook feuert überhaupt nicht. Wenn Sie so ein Plugin nutzen, deployen Sie die CSP besser auf Webserver-Ebene (Apache oder Nginx), damit gecachte Responses den Header trotzdem tragen.

Inline-Skripte vom Theme-Customizer

Der WordPress-Theme-Customizer schreibt Inline-CSS in <style>-Tags im Header. Das braucht 'unsafe-inline' in style-src. Ohne einen nonce-basierten Ansatz gibt's keinen sauberen Workaround.

Eine CSP für Ihr genaues WordPress-Setup → Erkennt Ihr Theme, Ihre Plugins und jedes externe Widget automatisch. Kein manuelles Freigeben.