La Content-Security-Policy (CSP) est un en-tête de réponse HTTP qui indique au navigateur quelles sources de contenu il a le droit de charger et d’exécuter. Bien posée, elle transforme une faille d’injection de script en simple entrée bloquée dans la console. Mal comprise, elle se résume à une ligne recopiée qui n’arrête rien ou qui casse la moitié du site. Ce guide décrit un déploiement progressif, des directives au mode de test, pour un socle qui protège sans paralyser la production.
Ce que verrouille une Content-Security-Policy
Le cross-site scripting (XSS) reste l’une des vulnérabilités web les plus répandues : un attaquant parvient à injecter du JavaScript dans une page, et ce code s’exécute avec les droits de la victime. La CSP agit en défense en profondeur. Même si une injection passe la validation d’entrée, le navigateur refuse d’exécuter un script dont l’origine n’est pas explicitement autorisée par la politique. L’en-tête ne remplace pas l’échappement des données, il ajoute une seconde barrière qui limite fortement l’impact d’une erreur applicative.
Anatomie de l’en-tête : les directives à connaître
Une politique est une liste de directives séparées par des points-virgules. Chaque directive nomme un type de ressource et la liste des sources autorisées, exprimées en mots-clés ('self', 'none') ou en domaines.
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; connect-src 'self' https://api.exemple.com; object-src 'none'| Directive | Rôle |
|---|---|
default-src | Valeur de repli pour les directives non précisées |
script-src | Origines autorisées pour le JavaScript |
style-src | Origines autorisées pour les feuilles de style |
img-src | Origines des images (souvent 'self' data:) |
connect-src | Cibles de fetch, XHR, WebSocket, EventSource |
frame-ancestors | Qui peut inclure la page dans une iframe (anti-clickjacking) |
base-uri | Restreint la balise <base>, souvent 'self' |
object-src | Plugins hérités, à mettre à 'none' |
La directive frame-ancestors mérite une mention à part : elle remplace l’ancien en-tête X-Frame-Options et contrôle qui peut afficher la page dans un cadre, ce qui coupe court aux attaques de type clickjacking.
Le piège de ‘unsafe-inline’ : nonces et empreintes
La tentation la plus courante consiste à ajouter 'unsafe-inline' à script-src pour que les scripts embarqués dans le HTML continuent de fonctionner. Ce mot-clé annule l’essentiel de la protection : il autorise justement le type de script qu’un attaquant cherche à injecter. Deux mécanismes permettent d’autoriser un script inline précis sans ouvrir la porte à tous les autres. Le nonce est une valeur aléatoire, régénérée à chaque réponse, posée à la fois dans l’en-tête et sur la balise.
<!-- En-tête : script-src 'nonce-r4Nd0mBase64' 'strict-dynamic' -->
<script nonce="r4Nd0mBase64">
// ce bloc précis est autorisé, aucun autre inline ne l'est
</script>L’empreinte ('sha256-…') suit la même logique pour un script au contenu figé. Le mot-clé 'strict-dynamic' complète le dispositif : la confiance accordée à un script muni d’un nonce se propage aux scripts qu’il charge, ce qui évite d’entretenir une longue liste de domaines.
Ajouter ‘unsafe-inline’ à script-src revient à autoriser précisément le type de code qu’une attaque XSS cherche à injecter.
Déployer sans casser la production : le mode Report-Only
Activer une politique stricte d’un coup sur un site existant provoque presque toujours des ruptures : une balise de suivi, une police externe, un widget tiers se retrouvent bloqués. L’en-tête Content-Security-Policy-Report-Only résout ce problème. Il applique la politique en observation : le navigateur ne bloque rien, mais signale chaque violation à un point de collecte. La liste des ressources refusées permet d’ajuster la politique avant de la faire respecter pour de bon.
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;Point de vigilance : commencez toujours par Report-Only sur un site en production, laissez tourner plusieurs jours pour couvrir tous les parcours, puis basculez sur l’en-tête bloquant. Vérifiez aussi que le proxy ou le CDN ne réécrit pas l’en-tête, sous peine d’appliquer une politique différente de celle prévue.
Collecter et lire les rapports de violation
Le mécanisme historique, report-uri, demande au navigateur d’envoyer un document JSON à chaque violation, décrivant la page concernée, la directive enfreinte et la ressource bloquée. L’approche moderne associe l’en-tête Reporting-Endpoints à la directive report-to, mieux intégrée à l’API de reporting du navigateur. Une route serveur légère suffit à recueillir ces envois.
{
"csp-report": {
"document-uri": "https://exemple.com/compte",
"violated-directive": "script-src 'self'",
"blocked-uri": "https://cdn.tiers.com/widget.js"
}
}La lecture régulière de ces rapports met au jour les sources tierces à autoriser explicitement : une police servie depuis un CDN, une carte ou une vidéo intégrée, un script de mesure d’audience. Chacune devient une ligne assumée de la politique, jamais un blanc-seing accordé à toute une catégorie.
Un socle de départ raisonnable
Pour un site classique servi par Nginx, une politique restrictive mais fonctionnelle peut servir de base, à élargir directive par directive selon les remontées du mode observation.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests" always;La directive upgrade-insecure-requests demande au navigateur de convertir les requêtes HTTP résiduelles en HTTPS, un filet utile lors d’une migration. Chaque ajout de source doit répondre à un besoin identifié, jamais à un blocage réglé par facilité avec 'unsafe-inline'.
Ce qu’il faut retenir
La CSP est une couche de défense qui limite l’impact d’une injection quand tout le reste a échoué. La méthode qui tient repose sur trois principes : bannir 'unsafe-inline' au profit des nonces ou des empreintes, déployer d’abord en Report-Only pour cartographier les ressources légitimes, puis resserrer directive par directive. Une politique posée dans cet ordre protège durablement sans transformer chaque mise en production en chasse aux régressions.
Sur les sites que je maintiens, je pose toujours la CSP en Report-Only pendant au moins une semaine avant de la rendre bloquante, et je centralise les rapports pour les relire à froid. C’est fastidieux, mais c’est la seule façon que j’ai trouvée d’éviter le scénario classique : une politique stricte activée un vendredi soir, et le formulaire de contact qui ne répond plus le lundi. La CSP n’est pas un interrupteur, c’est un réglage progressif. — Simon Janvier
Pour aller plus loin
Référence des directives et de leur syntaxe : Content-Security-Policy sur MDN Web Docs.
