Aller au contenu

Le média des artisans du web jeudi 20 août 2026

Sécurité

Content Security Policy : construire une politique qui tient

Une Content Security Policy bien construite bloque l'exécution des scripts injectés sans casser le site, à condition d'être déployée par étapes. Ce guide détaille les directives utiles, la méthode d'observation avant blocage et les pièges des sites tiers.

Une Content Security Policy bien construite transforme la plupart des failles d’injection de script en simples lignes de journal. Encore faut-il la déployer sans casser le site, ce qui suppose une méthode progressive plutôt qu’un en-tête copié depuis un exemple générique. Ce guide détaille les directives qui comptent, la façon de les mettre en place en observation avant de les rendre bloquantes, et les pièges propres aux sites riches en scripts tiers.

À quoi sert réellement une CSP

La Content Security Policy est un en-tête HTTP par lequel un serveur déclare au navigateur quelles sources de contenu sont légitimes pour une page. Le navigateur applique ensuite cette liste : un script chargé depuis un domaine non autorisé, un gestionnaire d’événement inline injecté par une attaque, ou un appel réseau vers un serveur pirate sont bloqués avant exécution.

Le bénéfice principal porte sur les attaques par injection de script, communément appelées XSS. Même lorsqu’un attaquant parvient à insérer une balise <script> dans une page, une politique stricte l’empêche de s’exécuter faute de correspondre à une source déclarée. La CSP couvre aussi la protection contre l’inclusion dans une iframe tierce, via la directive frame-ancestors, qui remplace l’ancien en-tête X-Frame-Options.

Navigateur applique la CSP Source déclarée (self, CDN de confiance) autorisée Script inline injecte, domaine inconnu : bloque + rapport Endpoint de rapport report-to

Les directives qui structurent une politique

Une politique se lit comme une suite de directives séparées par des points-virgules, chacune nommant un type de ressource et ses sources autorisées. La directive default-src sert de filet : elle s’applique à tout type non couvert par une directive plus spécifique.

DirectiveContrôleValeur de départ raisonnable
default-srcFilet pour les types non spécifiés'self'
script-srcOrigine des scripts JavaScript'self' + nonce
style-srcOrigine des feuilles de style'self'
img-srcOrigine des images'self' data:
connect-srcCibles des requêtes fetch, XHR, WebSocket'self' + API métier
frame-ancestorsQui peut embarquer la page en iframe'self' ou 'none'
base-uriValeurs autorisées pour la balise base'self'
form-actionDestinations de soumission des formulaires'self'
object-srcPlugins hérités (Flash, applets)'none'

Trois mots-clés reviennent en permanence. La valeur 'self' autorise la même origine que la page. La valeur 'none' interdit toute source. Les valeurs 'unsafe-inline' et 'unsafe-eval', à l’inverse, rouvrent la porte au script inline et à l’évaluation dynamique, et vident la politique d’une bonne partie de son intérêt dès qu’elles concernent script-src.

La méthode moderne : nonces et strict-dynamic

Autoriser une liste de domaines de confiance dans script-src fonctionne, mais cette approche vieillit mal : chaque nouveau service tiers allonge la liste, et un seul domaine autorisé compromis suffit à contourner la politique. L’approche recommandée aujourd’hui repose sur les nonces, des jetons aléatoires générés à chaque réponse et rattachés aux scripts légitimes.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m2026' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.exemple.fr;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';
  upgrade-insecure-requests;

Côté page, chaque script de confiance porte l’attribut correspondant, avec un jeton régénéré à chaque requête et jamais réutilisé.

<script nonce="r4nd0m2026" src="/assets/app.js"></script>

Le mot-clé 'strict-dynamic' complète le dispositif : un script déjà autorisé par nonce peut charger à son tour d’autres scripts, sans que chaque dépendance ait besoin d’être listée. La politique se concentre alors sur la racine de confiance plutôt que sur une énumération de domaines qui dérive avec le temps.

Une liste de domaines autorisés grandit à chaque intégration ; un nonce, lui, ne fait confiance qu’au code que le serveur a réellement émis.

Déployer sans casser le site

Le risque d’une CSP n’est pas la faille, c’est la régression : une directive trop stricte fait disparaître une carte, un formulaire de paiement ou un traceur analytique. La parade tient en un en-tête distinct, Content-Security-Policy-Report-Only, qui applique exactement les mêmes règles mais sans rien bloquer. Le navigateur se contente de signaler ce qu’il aurait refusé.

Méthode en trois temps. Publier d’abord la politique en Report-Only et collecter les violations pendant plusieurs jours de trafic réel. Corriger ensuite les sources légitimes qui remontent (widgets, polices, API). Basculer enfin sur l’en-tête bloquant une fois le flux de rapports devenu silencieux.

La collecte des rapports passe par la directive report-to, adossée à l’API Reporting, qui envoie un document JSON à un point de terminaison à chaque violation. Pour une compatibilité plus large avec les navigateurs anciens, l’ancienne directive report-uri reste souvent maintenue en parallèle le temps de la transition.

Les pièges des sites riches en tiers

Un site vitrine servi par un serveur maîtrisé se prête bien aux nonces. Un site bâti sur un CMS extensible pose un problème différent : de nombreux greffons injectent leurs propres balises <script> inline, sans nonce, au moment du rendu. Sur WordPress, par exemple, la tentation de rétablir 'unsafe-inline' pour faire taire les erreurs revient à désactiver la protection sur le point le plus sensible.

Trois réflexes limitent la casse. D’abord, inventorier les scripts tiers réellement utilisés et retirer ceux qui ne servent plus. Ensuite, préférer les extensions qui permettent d’attacher un nonce, ou déporter les scripts inline vers des fichiers servis depuis la même origine. Enfin, traiter connect-src avec autant de soin que script-src : c’est la directive qui empêche l’exfiltration de données vers un serveur tiers, souvent l’objectif final d’une injection réussie.

Tester et maintenir la politique dans le temps

Une CSP n’est jamais figée : chaque nouvelle fonctionnalité, chaque intégration marketing, chaque mise à jour d’extension peut introduire une source légitime que la politique ignore. Sans discipline de maintenance, deux dérives guettent. La première voit la liste des sources autorisées gonfler au fil des demandes urgentes, jusqu’à autoriser large et vider la protection de son sens. La seconde voit la politique bloquer silencieusement une fonctionnalité que personne ne teste, jusqu’à ce qu’un utilisateur signale le dysfonctionnement.

La console du navigateur reste le premier outil de diagnostic : chaque violation y apparaît avec la directive fautive et la ressource refusée, ce qui suffit à identifier une régression pendant le développement. Pour une évaluation plus systématique, des analyseurs en ligne notent une politique et pointent les mots-clés qui l’affaiblissent, au premier rang desquels 'unsafe-inline' et les jokers trop larges. Intégrer cette vérification à la chaîne d’intégration continue évite qu’une politique se dégrade sans que personne ne s’en aperçoive.

La maintenance gagne à être cadrée par quelques règles simples : documenter la raison de chaque source autorisée, revoir la politique à chaque ajout de service tiers, et conserver l’endpoint de rapport actif même après le passage en mode bloquant. Les violations qui continuent d’arriver en production signalent soit une attaque en cours, soit une fonctionnalité légitime oubliée lors du durcissement. Dans les deux cas, l’information mérite d’être vue.

Ce qu’il faut retenir

  • La CSP neutralise l’exécution des scripts injectés, à condition d’éviter 'unsafe-inline' sur script-src.
  • Les nonces associés à 'strict-dynamic' remplacent avantageusement les listes de domaines.
  • Le déploiement se fait en Report-Only d’abord, puis en mode bloquant une fois les faux positifs traités.
  • frame-ancestors, base-uri, form-action et object-src 'none' complètent la protection au-delà des seuls scripts.

La première fois que j’ai déployé une CSP directement en mode bloquant, j’ai cassé le tunnel de paiement d’un client en pleine journée. Depuis, je ne démarre plus jamais autrement qu’en Report-Only, et je laisse tourner une semaine complète avant de serrer la vis. Sur les sites WordPress chargés d’extensions, je considère qu’une CSP à moitié stricte mais réellement en place vaut mieux qu’une politique parfaite sur le papier que personne n’ose activer. — Simon Janvier

Pour aller plus loin

Référence des directives et de leur prise en charge : Content-Security-Policy sur MDN.

À lire aussi sur Mail Studio

Partager LinkedIn Bluesky Hacker News E-mail

À lire aussi