# Content Security Policy sur WordPress : la déployer sans casser les extensions

> Ajouter un en-tête CSP strict casse souvent des scripts inline d'extensions tierces. Méthode progressive avec mode report-only pour identifier les sources à autoriser avant de bloquer.

- Auteur : Clément Hadrot
- Publié le : 2023-06-27
- Mis à jour le : 2023-06-27
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/content-security-policy-wordpress-deployer/

## L’essentiel

- Un déploiement direct en mode bloquant casse presque toujours quelque chose
- Report-Only collecte les violations sans rien bloquer
- La liste finale se construit à partir de données réelles, pas de suppositions

La Content Security Policy (CSP) est l'un des en-têtes de sécurité les plus efficaces contre les attaques par injection de script (XSS) : elle indique explicitement au navigateur quelles sources de scripts, styles, images ou polices sont autorisées à s'exécuter sur une page, bloquant tout le reste par défaut. Sur un site WordPress classique, chargé d'extensions et de scripts tiers accumulés au fil des années, la déployer sans méthode revient presque systématiquement à casser une fonctionnalité quelque part — un chat en ligne qui ne s'affiche plus, un formulaire qui ne se soumet plus, une police qui ne charge plus.

Cette procédure décrit la méthode que nous suivons systématiquement pour déployer une CSP sur un site WordPress existant, en s'appuyant sur le mode report-only pour observer avant d'agir, plutôt que de bloquer à l'aveugle et de corriger les signalements des utilisateurs au fil de l'eau.

## Étape 1 : comprendre les deux en-têtes disponibles

Deux en-têtes HTTP distincts permettent de mettre en œuvre une CSP, et leur différence est le cœur de cette méthode progressive :

- `Content-Security-Policy` applique réellement les règles : toute ressource ne correspondant pas à une source autorisée est bloquée par le navigateur, immédiatement.
- `Content-Security-Policy-Report-Only` ne bloque rien : le navigateur continue de charger toutes les ressources normalement, mais envoie un rapport détaillé de chaque violation qu'il aurait bloquée si la politique avait été appliquée en mode strict.

Déployer directement le premier en-tête sur un site en production revient à découvrir les scripts cassés par les retours des utilisateurs. Déployer le second permet de collecter, sans aucun risque pour le site en fonctionnement, la liste exhaustive des sources réellement utilisées.

## Étape 2 : mettre en place la collecte en report-only

> L'essentiel à retenir : Un déploiement direct en mode bloquant casse presque toujours quelque chose ; Report-Only collecte les violations sans rien bloquer ; La liste finale se construit à partir de données réelles, pas de suppositions

```
add_action( 'send_headers', function() {
    if ( is_admin() ) {
        return;
    }

    $politique = "default-src 'self'; "
        . "report-uri /wp-json/moncpt/v1/csp-rapport";

    header( "Content-Security-Policy-Report-Only: {$politique}" );
} );
```

La directive `report-uri` pointe vers un point d'entrée REST personnalisé qui enregistre chaque violation reçue, plutôt que de les perdre ou de dépendre d'un service tiers externe :

```
add_action( 'rest_api_init', function() {
    register_rest_route( 'moncpt/v1', '/csp-rapport', array(
        'methods'             => 'POST',
        'callback'            => 'moncpt_enregistrer_violation_csp',
        'permission_callback' => '__return_true',
    ) );
} );

function moncpt_enregistrer_violation_csp( $request ) {
    $donnees = $request->get_json_params();
    $ligne   = wp_json_encode( $donnees ) . "\n";

    file_put_contents(
        WP_CONTENT_DIR . '/csp-violations.log',
        $ligne,
        FILE_APPEND | LOCK_EX
    );

    return new WP_REST_Response( null, 204 );
}
```

Le fichier de journalisation doit être protégé de l'accès public par une règle serveur, exactement comme n'importe quel autre journal sensible, pour éviter d'exposer par ce biais des informations sur l'architecture interne du site.

## Étape 3 : laisser tourner et analyser le trafic réel

Sur nos projets, cette collecte reste active au minimum deux semaines, une durée qui permet de couvrir un cycle d'usage complet du site (jours de semaine, week-ends, éventuels pics d'activité mensuels) sans manquer une source légitime mais peu fréquente. L'analyse du journal fait apparaître des motifs clairs : des scripts chargés depuis un CDN de police, un widget de chat tiers hébergé sur son propre domaine, des pixels de suivi publicitaire, parfois un script inline ajouté directement dans un widget de texte par un ancien administrateur, sans passer par un fichier externe.

```
# Extraction rapide des domaines distincts signalés en violation
grep -oP '"blocked-uri":"\K[^"]+' wp-content/csp-violations.log \
  | sed -E 's#^(https?://[^/]+).*#\1#' \
  | sort | uniq -c | sort -rn
```

## Étape 4 : construire la politique définitive et basculer en blocage

À partir de cette liste réelle, la politique finale se construit source par source, jamais en autorisant des domaines entiers par précaution :

```
$politique = implode( '; ', array(
    "default-src 'self'",
    "script-src 'self' https://cdn.chat-tiers.example",
    "style-src 'self' https://fonts.googleapis.com 'unsafe-inline'",
    "font-src 'self' https://fonts.gstatic.com",
    "img-src 'self' data: https://pixel-analytics.example",
    "report-uri /wp-json/moncpt/v1/csp-rapport",
) );

header( "Content-Security-Policy: {$politique}" );
```

Le script inline découvert dans un widget de texte n'a pas été autorisé via `'unsafe-inline'` pour `script-src`, une directive qui annulerait une grande partie de la protection contre le XSS : il a plutôt été déplacé vers un fichier JavaScript externe chargé depuis le domaine du site lui-même, une correction plus propre qui renforce la sécurité au lieu de la contourner.

## Ne jamais basculer sans une seconde phase de report-only sur la politique finale

Avant de retirer complètement `Content-Security-Policy-Report-Only` et de ne garder que la version bloquante, nous faisons cohabiter les deux en-têtes une semaine supplémentaire : la version stricte bloque déjà réellement, tandis que la version report-only, avec une politique légèrement plus permissive en parallèle, continue de signaler les cas limites qui auraient pu échapper à la première collecte, notamment les fonctionnalités utilisées rarement (un formulaire de contact saisonnier, par exemple).

> Sur nos déploiements de CSP, la règle non négociable reste : jamais de bascule directe en mode bloquant sans au moins deux semaines de collecte préalable en report-only. C'est le seul moyen de construire une politique à partir de données réelles plutôt que de suppositions sur ce que le site charge.

## En résumé

Cette méthode ne couvre pas les autres en-têtes de sécurité HTTP (comme `X-Frame-Options` ou `Strict-Transport-Security`), traités par ailleurs et généralement moins susceptibles de casser des fonctionnalités existantes. La CSP reste l'en-tête le plus puissant contre le XSS, mais aussi le plus exigeant à déployer correctement sur un site WordPress mature, précisément à cause de l'accumulation de scripts tiers qui caractérise ce type de projet.
