Si vous avez déjà vu passer une alerte “Refused to frame …” ou “Content Security Policy violation” dans la console du navigateur, vous avez touché du doigt un vrai sujet : les headers HTTP de sécurité. Sur WordPress 6.9.4 (avril 2026), ils ne remplacent pas un code propre ni des mises à jour régulières, mais ils réduisent fortement l’impact de plusieurs attaques courantes (XSS, clickjacking, downgrade HTTPS) et ils rendent certaines compromissions plus difficiles à monétiser.
La menace
Les headers HTTP de sécurité ne “réparent” pas WordPress. Ils changent les règles du navigateur. Et c’est précisément ce qui gêne l’attaquant.
Ce qu’un attaquant peut faire sans ces headers
- Exécuter du JavaScript injecté (XSS) : si un plugin ou un thème a une faille XSS, l’attaquant peut voler des cookies (quand ils ne sont pas HttpOnly), détourner une session, injecter une fausse page de paiement, ou créer un nouvel admin via l’interface.
- Piéger vos visiteurs via du clickjacking : votre wp-admin, une page de changement d’email, ou une action sensible (désactiver un plugin de sécurité, ajouter un utilisateur) peut être “encapsulée” dans un iframe invisible au-dessus d’un bouton “Jouer”. Le visiteur clique, mais il clique chez vous.
- Forcer un downgrade HTTPS → HTTP (ou exploiter des zones mixtes) : sans HSTS, un attaquant en Wi‑Fi public peut tenter de rediriger vers HTTP (ou intercepter des requêtes non protégées). Ce scénario est moins fréquent qu’avant, mais je le vois encore sur des sites “partiellement HTTPS” ou des domaines secondaires oubliés.
- Exfiltrer des données via des chargements externes : scripts, iframes, images, polices… Le navigateur est très permissif par défaut. Une CSP bien pensée limite ce que la page peut charger.
Fréquence réelle (ce que j’observe sur des sites WordPress)
Sur la majorité des nettoyages WordPress que j’ai menés ces dernières années, le point d’entrée initial est presque toujours un plugin vulnérable (ou un compte admin compromis). Ensuite, l’attaquant cherche à persister : injection de JS dans le thème, insertion de scripts dans la base, création de comptes, redirections SEO spam. Une CSP n’empêche pas l’infection initiale, mais elle peut empêcher l’exécution de certains scripts injectés, et donc casser la monétisation (skimmer, redirect, pub fraud).
Explication simple
Ces headers sont des “garde-fous côté navigateur”. Vous dites au navigateur : “n’exécute pas de scripts inline”, “n’autorise pas mon site dans un iframe”, “n’utilise que HTTPS pendant un an”. Si un attaquant injecte du code, le navigateur peut refuser de l’exécuter.
Résumé rapide
- CSP (Content-Security-Policy) limite les sources de scripts/styles/images et réduit l’impact d’une XSS, mais demande des ajustements (surtout avec des page builders).
- HSTS (Strict-Transport-Security) force HTTPS côté navigateur. À activer uniquement si tout votre site (et sous-domaines si inclus) est proprement en HTTPS.
- X-Frame-Options (ou frame-ancestors en CSP) protège du clickjacking. Pour WordPress, c’est souvent “DENY” ou “SAMEORIGIN”.
- Ne mettez pas ces headers au hasard dans functions.php : à cause du cache, des headers déjà envoyés, des pages servies sans passer par PHP, et des conflits plugins/CDN.
- Testez d’abord en Report-Only pour CSP, surtout si vous utilisez Elementor, Divi 5 ou Avada (beaucoup d’inline et de scripts tiers).
- Le meilleur endroit pour des headers fiables reste le serveur (Nginx/Apache) ou le CDN/WAF, avec une couche WordPress uniquement si nécessaire.
Code vulnérable — ce qu’il ne faut PAS faire
Voici un “mauvais” snippet que je croise encore dans des tutos ou des plugins de snippets. Il illustre deux problèmes : (1) une CSP inefficace voire dangereuse, (2) l’ajout de headers au mauvais moment, qui casse aléatoirement le site.
<?php
/**
* MAUVAIS EXEMPLE — Ne copiez pas ce code.
* Problèmes :
* - CSP trop permissive (unsafe-inline/unsafe-eval), ne bloque presque rien.
* - Ajout des headers sur 'wp_head' (trop tard), risque "headers already sent".
* - Pas de gestion des pages admin / REST / fichiers servis par cache.
*/
add_action('wp_head', function () {
header("Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;");
header("X-Frame-Options: ALLOWALL");
header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload");
});
Pourquoi c’est dangereux (sans donner d’outils d’exploit)
- “unsafe-inline” et “unsafe-eval” neutralisent une grande partie de l’intérêt de CSP. Beaucoup d’injections XSS reposent sur du script inline ou sur des constructions évaluées. En autorisant tout, vous donnez l’illusion d’être protégé.
- X-Frame-Options: ALLOWALL ouvre la porte au clickjacking. Un attaquant peut intégrer vos pages dans un iframe et piéger des clics.
- HSTS preload/includeSubDomains peut vous enfermer. Si un sous-domaine (ex.
old.blog.example.com) n’a pas de TLS valide, vous le rendez inaccessible. J’ai déjà vu une entreprise se bloquer elle-même pendant une migration parce qu’un sous-domaine de tracking n’était pas prêt. - Le hook wp_head est trop tardif. Le HTML commence souvent à sortir avant (BOM UTF‑8, espaces, plugin qui “echo”), et PHP ne peut plus envoyer de headers. Résultat : erreurs aléatoires, ou headers absents sur certaines pages.
Tableau de diagnostic (symptômes typiques)
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| Erreur “Cannot modify header information – headers already sent” | Header envoyé après sortie HTML (hook trop tard, BOM, echo) | Activer WP_DEBUG_LOG et consulter wp-content/debug.log |
Déplacer la logique sur send_headers ou au serveur/CDN |
| Le site marche, mais les headers n’apparaissent pas | Cache page/CDN sert une version sans passer par PHP | curl -I https://… sur plusieurs URLs |
Configurer les headers au niveau serveur/CDN |
| Elementor/Divi “casse” (styles absents, JS bloqué) | CSP trop stricte sans phase Report-Only | Console navigateur → onglet “Security/Console” CSP | Déployer CSP en Report-Only, ajouter les sources nécessaires |
| Back-office encadré dans un iframe sur un autre domaine | X-Frame-Options absent ou permissif | DevTools → Network → Response headers | Mettre frame-ancestors ou X-Frame-Options: SAMEORIGIN |
Code sécurisé — la bonne implémentation
Je privilégie une approche en deux étages : (1) le serveur/CDN pour la fiabilité, (2) WordPress pour les exceptions (URLs spécifiques, environnements de staging, compatibilité builder). Le code ci-dessous vise WordPress 6.9.4+ et PHP 8.1+.
Objectif réaliste (CSP “starter” + anti-framing + HSTS conditionnel)
- Mettre X-Frame-Options (fallback) et surtout CSP frame-ancestors.
- Déployer une CSP en Report-Only au début, puis passer en enforcement.
- Activer HSTS seulement si HTTPS est garanti (et en évitant de casser des sous-domaines).
Implémentation WordPress (mu-plugin recommandé)
Au lieu de coller ça dans functions.php (risque à chaque changement de thème), je le mets souvent dans un mu-plugin : wp-content/mu-plugins/security-headers.php. Un mu-plugin se charge tôt et ne dépend pas du thème.
<?php
/**
* Plugin Name: BPCAB - Security Headers
* Description: Ajoute des headers de sécurité (CSP, HSTS, anti-framing) avec garde-fous.
* Author: Votre équipe
* Version: 1.0.0
*
* Ce fichier doit être placé dans wp-content/mu-plugins/security-headers.php
*
* Cible : WordPress 6.9.4+ / PHP 8.1+
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Détermine si la requête est servie en HTTPS de façon fiable.
* On prend en compte les reverse proxies/CDN via les headers standards.
*/
function bpcab_is_https_request(): bool
{
if (is_ssl()) {
return true;
}
// Cas reverse proxy (Cloudflare, ELB, etc.) : à adapter à votre infra.
$proto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
if (is_string($proto) && strtolower($proto) === 'https') {
return true;
}
$cf = $_SERVER['HTTP_CF_VISITOR'] ?? '';
if (is_string($cf) && stripos($cf, '"scheme":"https"') !== false) {
return true;
}
return false;
}
/**
* Construit une CSP "starter" adaptée à beaucoup de sites WordPress.
* Stratégie :
* - On évite unsafe-inline autant que possible.
* - On autorise les assets depuis 'self' et https:
* - On prévoit data: pour images (souvent utilisé par les builders).
*
* Note : Cette CSP est volontairement prudente mais pas parfaite.
* Sur Elementor/Divi/Avada, vous devrez souvent ajouter des domaines (polices, vidéos, analytics).
*/
function bpcab_build_csp(bool $report_only = true): array
{
$directives = [];
// Base
$directives[] = "default-src 'self'";
$directives[] = "base-uri 'self'";
$directives[] = "object-src 'none'";
$directives[] = "frame-ancestors 'self'"; // Anti-clickjacking (préférable à X-Frame-Options)
// Scripts : WordPress + plugins chargent souvent depuis le domaine du site.
// Si vous avez des scripts tiers (GTM, reCAPTCHA, etc.), ajoutez-les explicitement.
$directives[] = "script-src 'self' https:";
// Styles : beaucoup de thèmes/builders injectent du inline CSS.
// En idéal : pas de 'unsafe-inline'. En pratique : vous devrez parfois l'ajouter.
$directives[] = "style-src 'self' https:";
// Images : data: est utile pour les images inline et certains placeholders.
$directives[] = "img-src 'self' https: data:";
// Polices
$directives[] = "font-src 'self' https: data:";
// AJAX / REST / API externes
$directives[] = "connect-src 'self' https:";
// Iframes (YouTube/Vimeo, formulaires, etc.) : à restreindre selon votre usage.
$directives[] = "frame-src 'self' https:";
// Empêche le navigateur de "deviner" le type MIME
// (ce n'est pas CSP, mais on le gère dans le même endroit)
$header_name = $report_only ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy';
return [
'name' => $header_name,
'value' => implode('; ', $directives),
];
}
/**
* Ajoute les headers au bon moment.
* send_headers est un bon compromis : WordPress a routé la requête,
* mais rien n'est encore envoyé (si le site est propre).
*
* Attention : sur des pages servies par cache (plugin, Varnish, CDN),
* ce code peut ne pas s'exécuter. D'où l'importance de la config serveur/CDN.
*/
add_action('send_headers', function () {
// Évitez d'appliquer une CSP stricte sur certaines réponses (feeds, robots, etc.) si besoin.
if (is_feed()) {
return;
}
// 1) Anti-framing (fallback legacy)
// X-Frame-Options est moins flexible que CSP frame-ancestors, mais encore utile.
header('X-Frame-Options: SAMEORIGIN');
// 2) Anti-MIME sniffing
header('X-Content-Type-Options: nosniff');
// 3) Referrer policy (réduit la fuite d'URL)
header('Referrer-Policy: strict-origin-when-cross-origin');
// 4) Permissions Policy (à ajuster selon votre site)
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
// 5) CSP : commencez en Report-Only sur un site existant
$report_only = true;
// Exemple : désactiver Report-Only sur staging pour valider, puis activer en prod.
// if (defined('WP_ENVIRONMENT_TYPE') && WP_ENVIRONMENT_TYPE === 'production') {
// $report_only = false;
// }
$csp = bpcab_build_csp($report_only);
header($csp['name'] . ': ' . $csp['value']);
// 6) HSTS : uniquement si HTTPS est garanti
if (bpcab_is_https_request()) {
// Démarrez avec un max-age faible (ex. 1 jour) puis augmentez.
// N'ajoutez includeSubDomains/preload qu'après audit complet.
header('Strict-Transport-Security: max-age=86400');
}
}, 10);
Explication “simple” (ce que ça change pour vous)
- send_headers évite une partie des “headers already sent”. Ce n’est pas magique : un plugin qui echo trop tôt peut encore tout casser, mais c’est déjà beaucoup plus fiable que
wp_head. - CSP en Report-Only vous permet de voir ce qui serait bloqué, sans casser le front. Sur des sites avec Elementor/Divi/Avada, c’est presque obligatoire.
- HSTS à 86400 (1 jour) est un garde-fou de départ. Vous montez ensuite à 1 mois, puis 1 an si tout est propre.
Explication “technique” (détails qui évitent les pièges)
- Pourquoi pas unsafe-inline par défaut ? Parce que c’est la porte d’entrée la plus fréquente pour l’exécution d’injections. Si vous devez l’ajouter, faites-le temporairement et cherchez la cause (builder, plugin, inline CSS/JS).
- Pourquoi frame-ancestors + X-Frame-Options ? Parce que
frame-ancestorsest la règle moderne, mais X-Frame-Options reste un fallback utile. Les navigateurs modernes respectent frame-ancestors. - Pourquoi pas “upgrade-insecure-requests” ? Je l’ajoute parfois, mais uniquement après avoir vérifié qu’il n’y a pas de ressources HTTP cassées. Sinon vous masquez un problème et vous le découvrez plus tard avec des assets qui ne chargent plus.
- Pourquoi pas report-uri / report-to ? Les mécanismes de reporting CSP ont évolué et dépendent de votre stack. Sur WordPress “blog”, je préfère commencer par la console + logs serveur, puis mettre un endpoint de reporting si vous avez déjà une brique d’observabilité.
Compatibilité page builders (Divi 5, Elementor, Avada)
Les builders modernes génèrent souvent du inline CSS, parfois du inline JS, et chargent des assets depuis des domaines tiers (polices, vidéos, analytics). Trois cas que je rencontre souvent :
- Elementor : des widgets tiers ajoutent des scripts depuis des CDN. Votre CSP doit autoriser ces domaines dans
script-srcet parfoisstyle-src. - Divi 5 : certains modules injectent du style inline. Si vous refusez
'unsafe-inline'dansstyle-src, vous devrez migrer vers des feuilles CSS générées/externes quand c’est possible, ou accepter temporairement unsafe-inline pour les styles. - Avada : souvent des iframes (maps, vidéos) et des polices. Ajustez
frame-srcetfont-src.
Piège classique : vous modifiez la CSP, mais vous ne voyez aucun changement car votre CDN ou votre plugin de cache sert une page en cache avec les anciens headers. Videz le cache CDN + cache plugin + cache navigateur, et testez avec
curl -I.
Configuration serveur
Si vous ne deviez choisir qu’un seul endroit pour gérer ces headers, ce serait le serveur (ou le CDN/WAF). WordPress ne voit pas toujours passer les requêtes (cache statique), et certains headers doivent être cohérents sur toutes les réponses.
Apache (.htaccess) — configuration copier/coller
Prérequis : modules mod_headers activés. Sur un hébergement mutualisé, c’est souvent déjà le cas.
# Vérifier rapidement si mod_headers est actif (si vous avez accès SSH)
apachectl -M | grep headers
Snippet .htaccess (à placer idéalement dans le VirtualHost, sinon dans le .htaccess à la racine WordPress). Adaptez la CSP à votre site.
# --- Security headers (Apache) ---
<IfModule mod_headers.c>
# Anti-clickjacking (fallback)
Header always set X-Frame-Options "SAMEORIGIN"
# Anti-MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# Réduit la fuite d'URL
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Permissions Policy (à ajuster)
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
# CSP : démarrez en Report-Only
Header always set Content-Security-Policy-Report-Only "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self' https:; style-src 'self' https:; img-src 'self' https: data:; font-src 'self' https: data:; connect-src 'self' https:; frame-src 'self' https:"
# HSTS : uniquement si le site est 100% HTTPS
# Démarrez petit (1 jour), puis augmentez.
Header always set Strict-Transport-Security "max-age=86400" env=HTTPS
</IfModule>
Nginx — exemple de configuration
Sur Nginx, mettez ces directives dans le bloc server HTTPS. Évitez de les mettre dans le server HTTP (port 80) pour HSTS.
# --- Security headers (Nginx) ---
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP : commencez en Report-Only
add_header Content-Security-Policy-Report-Only "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self' https:; style-src 'self' https:; img-src 'self' https: data:; font-src 'self' https: data:; connect-src 'self' https:; frame-src 'self' https:" always;
# HSTS : activez seulement sur HTTPS
add_header Strict-Transport-Security "max-age=86400" always;
wp-config.php — ce que vous pouvez (et ne pouvez pas) faire
wp-config.php n’est pas un bon endroit pour envoyer des headers. En revanche, c’est un bon endroit pour verrouiller HTTPS côté WordPress et réduire des comportements dangereux.
<?php
// wp-config.php (extraits)
// Force l'admin en HTTPS (utile si une partie du site est mal configurée)
define('FORCE_SSL_ADMIN', true);
// Désactive l'éditeur de fichiers dans wp-admin (réduit l'impact d'un compte admin compromis)
define('DISALLOW_FILE_EDIT', true);
// Sur certains hébergements, limiter la capacité d'écriture peut aider (à évaluer selon votre workflow)
// define('DISALLOW_FILE_MODS', true);
CDN/WAF (Cloudflare, Fastly, etc.)
Si vous avez un CDN, mettez les headers là-bas. C’est souvent la seule façon d’être sûr qu’ils sont présents sur les pages servies depuis le cache edge. Le piège : ne dupliquez pas des CSP différentes entre CDN et origin, sinon vous allez déboguer des incohérences pendant des heures.
Vérifier si votre site est vulnérable
On ne “scanne” pas une vulnérabilité unique ici. On vérifie l’absence, l’incohérence ou la dangerosité des headers, et on cherche les endroits où une CSP stricte casserait le site.
Vérifier les headers avec curl
# Page d'accueil
curl -I https://example.com/
# Une page interne (builder, shortcode, etc.)
curl -I https://example.com/ma-page/
# Une URL wp-admin (doit aussi être protégée contre framing)
curl -I https://example.com/wp-admin/
Ce que je cherche en pratique :
- Strict-Transport-Security présent sur HTTPS, absent sur HTTP.
- Content-Security-Policy (ou Report-Only) cohérent sur plusieurs URLs.
- X-Frame-Options en SAMEORIGIN ou DENY (selon votre besoin).
- Absence de valeurs “fourre-tout” du type
default-src *ouunsafe-evalsans justification.
Vérifier côté navigateur (CSP violations)
Ouvrez DevTools → Console, rechargez la page, et filtrez sur “CSP”. En Report-Only, vous verrez exactement quelles sources seraient bloquées.
WP-CLI : repérer les plugins qui injectent du inline
WP-CLI ne peut pas “voir” le DOM rendu, mais vous pouvez repérer des suspects : plugins de tracking, popups, builders, optimisation JS.
# Liste des plugins actifs
wp plugin list --status=active
# Trouver rapidement des plugins connus pour injecter du inline (exemple simple)
wp plugin list --status=active --field=name | grep -Ei "elementor|divi|avada|optimize|cache|analytics|pixel|gtm|recaptcha"
Logs serveur : indicateurs utiles
- Erreurs 403/404 sur des fichiers JS/CSS après ajout de CSP (souvent un effet indirect d’une migration/optimisation).
- Requêtes vers /wp-admin/ depuis des referers bizarres (tentatives d’iframe/clickjacking ne laissent pas toujours des traces évidentes, mais les patterns de referer peuvent aider).
- Erreurs “mixed content” : avant HSTS long, corrigez-les.
Erreurs de sécurité fréquentes
| Erreur | Risque | Solution |
|---|---|---|
Coller le snippet dans functions.php du thème parent |
Perte à la prochaine mise à jour du thème, incohérences, dépannage difficile | Utiliser un mu-plugin ou un plugin dédié, ou config serveur/CDN |
Utiliser le hook wp_head (ou wp_footer) pour envoyer des headers |
Headers non envoyés, erreurs “headers already sent” | Utiliser send_headers ou le serveur |
| Activer HSTS 1 an + includeSubDomains sans audit | Sous-domaines cassés, accès impossible (même après correction côté serveur, le navigateur “se souvient”) | Démarrer avec max-age=86400, tester, augmenter progressivement |
| Déployer une CSP en enforcement directement sur un site en production | Front cassé (CSS/JS bloqués), perte de conversions | Commencer en Content-Security-Policy-Report-Only, corriger, puis appliquer |
Ajouter 'unsafe-inline' et 'unsafe-eval' “pour que ça marche” |
CSP quasi inutile contre XSS | Limiter au strict nécessaire, identifier la source, migrer vers des assets externes |
| Oublier de vider le cache (plugin/CDN/navigateur) | Tests incohérents, faux positifs | Purger partout, tester avec curl -I + navigation privée |
| Dupliquer les headers (CDN + serveur + WordPress) avec des valeurs différentes | Comportements imprévisibles selon la route réseau | Définir une source d’autorité (CDN ou serveur), WordPress seulement pour exceptions |
| Copier un ancien tuto avec des directives obsolètes | Protection partielle, fausse confiance | Vérifier la doc navigateur et tester sur Chrome/Firefox/Safari actuels |
| Oublier un point-virgule dans un mu-plugin | Fatal error, site indisponible | Déployer sur staging, activer un mécanisme de rollback, surveiller les logs PHP |
Checklist de durcissement
- HTTPS partout (front + admin + endpoints) et correction du mixed content avant HSTS long.
- HSTS progressif : 1 jour → 1 semaine → 1 mois → 1 an (si tout est stable).
- Anti-framing :
frame-ancestors 'self'+X-Frame-Options: SAMEORIGIN(ou DENY si vous n’intégrez rien). - CSP en Report-Only au début, collecte des violations, puis passage en enforcement.
- Limiter les domaines tiers (scripts/iframes) : chaque domaine ajouté est une surface de risque.
- Une seule source d’autorité pour les headers (CDN ou serveur), éviter les doublons contradictoires.
- Tester les pages critiques : checkout, formulaire, login, pages builder complexes, pages multilingues.
- Vider les caches après chaque changement.
- Surveiller la console navigateur et les logs serveur 48h après déploiement.
- Documenter pourquoi chaque domaine est autorisé dans CSP (sinon ça devient ingérable en 6 mois).
Que faire si le site est déjà compromis ?
Les headers ne nettoient rien. Si vous avez un doute (redirections, scripts inconnus, comptes admin ajoutés), traitez ça comme un incident.
- Mettre le site en maintenance (ou au minimum bloquer l’admin) pour éviter que l’attaquant ne réinjecte pendant le nettoyage. Si vous avez un WAF, bloquez temporairement l’accès à
/wp-admin/par IP. - Faire une sauvegarde “forensique” (fichiers + base) avant toute modification. Vous en aurez besoin si vous devez comprendre l’entrée initiale.
- Changer tous les secrets : mots de passe WP (admins en premier), FTP/SSH, base de données, clés API, et regénérer les salts dans
wp-config.phpvia https://api.wordpress.org/secret-key/1.1/salt/. - Vérifier les utilisateurs (admins inconnus, emails modifiés). Supprimez/neutralisez tout compte suspect et forcez une réinitialisation des mots de passe.
- Réinstaller WordPress core proprement (mêmes versions) et comparer les fichiers. WP-CLI aide :
# Vérifie l'intégrité des fichiers core
wp core verify-checksums
# Réinstalle WordPress core (sans toucher wp-content)
wp core download --force
- Auditer wp-content : thèmes/plugins inutiles, fichiers PHP inconnus, timestamps bizarres. Dans mon expérience, les backdoors se cachent souvent dans
wp-content/uploads/(qui ne devrait pas contenir de PHP exécutable). - Mettre à jour WordPress 6.9.4+ et tous les plugins/thèmes. Supprimez ceux que vous n’utilisez pas.
- Scanner les points d’injection : options, widgets, header/footer builder, templates, mu-plugins. Cherchez des
<script>inattendus, des iframes, deseval, des base64. - Reprendre la main sur la chaîne de déploiement : si votre poste est compromis ou si des clés API ont fuité, vous reviendrez au même problème.
- Après nettoyage : seulement là, déployez/renforcez CSP, anti-framing, HSTS et surveillez.
Si vous avez une boutique ou un site avec paiement, considérez qu’une injection JS peut être un skimmer. Dans ce cas, impliquez votre prestataire de paiement et suivez leurs procédures d’incident.
Conseils de maintenance et compatibilité
Performance
- Les headers ont un coût négligeable. Le vrai coût vient d’une CSP mal gérée qui vous pousse à multiplier les exceptions.
- Si vous utilisez un plugin d’optimisation (minify/combine), testez : certains changent les URLs des assets, ce qui peut déclencher des violations CSP si vous aviez whitelisté un chemin via une stratégie trop stricte.
SEO
- HSTS n’améliore pas le SEO directement, mais il stabilise l’accès HTTPS (moins de variations HTTP/HTTPS).
- Une CSP trop restrictive peut casser le rendu (CSS/JS), ce qui peut impacter l’indexation si Googlebot ne peut pas exécuter/charger des ressources nécessaires.
Compatibilité future (WordPress 6.9.4+)
- Le cœur WordPress évolue, mais la logique “envoyer des headers” reste stable. Le point fragile est votre écosystème plugins/builders.
- Évitez les snippets trouvés sur des blogs anciens. Beaucoup datent d’une époque où CSP était comprise comme “mettre unsafe-inline partout”. En 2026, ça ne sert pas à grand-chose.
Quand vous devez autoriser du inline (cas réel)
Si un builder vous force à autoriser du inline style, faites-le de façon ciblée et temporaire, puis planifiez une réduction. Une stratégie pragmatique que j’ai déjà appliquée :
- Enforcement CSP mais
style-src 'unsafe-inline'uniquement (pas surscript-src). - Pas de unsafe-eval sauf nécessité absolue (et là, il faut remettre en question le plugin).
- Réduire la liste des domaines tiers à ceux réellement utilisés.
Ressources
- WordPress Developer Resources – Security APIs
- Hook WordPress send_headers (référence)
- WordPress.org – Hardening WordPress
- Dépôt GitHub WordPress (code source)
- WordPress Core Trac (tickets et historique)
- PHP.net – header()
- MDN – Content Security Policy (CSP)
- MDN – Strict-Transport-Security (HSTS)
- MDN – X-Frame-Options
FAQ
Dois-je utiliser X-Frame-Options si j’ai déjà frame-ancestors en CSP ?
Oui, en pratique je mets les deux : CSP frame-ancestors est la règle moderne et flexible, X-Frame-Options sert de fallback. Évitez ALLOW-FROM, mal supporté.
Quelle valeur choisir : SAMEORIGIN ou DENY ?
DENY est plus strict (aucun iframe, même depuis votre domaine). SAMEORIGIN autorise l’iframe depuis votre propre domaine. Si vous intégrez volontairement votre contenu dans un autre domaine (rare pour un blog), vous devrez passer par frame-ancestors avec une liste de domaines autorisés.
HSTS peut-il casser mon site ?
Oui. Si vous activez HSTS avec un max-age long alors que certains endpoints/sous-domaines ne servent pas HTTPS correctement, les navigateurs refuseront d’y accéder. Démarrez court (1 jour), puis augmentez.
Pourquoi je ne vois pas mes headers alors que j’ai ajouté le code ?
Dans 80% des cas : cache (plugin, Varnish, CDN). Testez avec curl -I, purgez le cache, et vérifiez si votre CDN réécrit/supprime des headers.
Est-ce que CSP va bloquer Google Analytics, Tag Manager, YouTube, reCAPTCHA ?
Oui, si vous ne les autorisez pas explicitement. C’est le but. Ajoutez ensuite les domaines nécessaires dans script-src, frame-src, connect-src selon le service.
Je suis sur Elementor/Divi/Avada et tout est en inline. Je fais quoi ?
Commencez par CSP Report-Only. Ensuite, acceptez éventuellement 'unsafe-inline' pour style-src (pas pour script-src), et réduisez progressivement. Une CSP “parfaite” est rarement réaliste sur un site builder existant sans refonte.
Où placer le code côté WordPress ?
Un mu-plugin est le plus fiable côté WP. Évitez le thème. Et si vous avez un CDN, préférez le CDN pour la version finale (WordPress pour les exceptions).
Est-ce que ces headers protègent wp-admin ?
Oui, s’ils sont envoyés aussi sur /wp-admin/. Testez spécifiquement cette URL. Le clickjacking sur des actions admin est un scénario réel si vous n’avez pas d’anti-framing.
Que faire si j’ai “headers already sent” après ajout du mu-plugin ?
Recherchez une sortie précoce : BOM UTF‑8 dans un fichier PHP, espaces avant <?php, un plugin qui fait echo trop tôt. Regardez wp-content/debug.log. En attendant, basculez la gestion des headers au serveur/CDN (plus robuste).
Puis-je tout faire avec un plugin de sécurité ?
Oui, certains plugins ajoutent ces headers. Le piège est la maintenance : vous ne savez plus quelle couche (plugin vs serveur vs CDN) gagne. Si vous utilisez un plugin, documentez et évitez les doublons côté serveur.