EXEMPLES DE CSP ET DÉPLOIEMENT

Exemples de Content-Security-Policy : construire une CSP stricte, étape par étape

L’en-tête Content-Security-Policy indique au navigateur quels scripts peuvent s’exécuter sur vos pages. Trois politiques d’exemple qui fonctionnent, ce qui rend une politique stricte (nonces, hashes et strict-dynamic), les directives que default-src ne couvre pas, les rapports de violation et un déploiement progressif qui commence en mode Report-Only.

Illustration conceptuelle d’un pavillon d’entrée en céramique ivoire avec une herse en verre : les sphères de verre portant un sceau menthe passent en s’illuminant, tandis qu’une sphère grise sans sceau reste dehors près d’un tampon en cuivre.
Illustration conceptuelle

La Content-Security-Policy (CSP) est un en-tête de réponse qui indique au navigateur quels scripts, styles, images, cadres et connexions une page a le droit d’utiliser. Si quelqu’un parvient à glisser un script dans votre HTML, une bonne politique empêche le navigateur de l’exécuter. Une politique copiée sur un forum échoue généralement de deux façons : elle casse l’analytics, les polices web et le module de paiement, ou bien on l’alourdit de 'unsafe-inline' et de jokers jusqu’à ce qu’elle autorise presque tout.

Ce guide propose des exemples qui fonctionnent pour trois types de sites, explique ce qui rend une politique stricte (nonces, hashes et 'strict-dynamic'), passe en revue les directives que default-src n’atteint pas, montre comment recueillir les rapports de violation et décrit un déploiement qui démarre en mode Report-Only, pour que rien ne casse sans prévenir. Tous les exemples utilisent example.com ; remplacez les valeurs d’exemple par les vôtres.

Portée de ce guide

Le vérificateur gratuit lit les en-têtes d’une seule réponse publique : la page d’accueil de l’origine saisie, après au plus cinq redirections. Il ne lit que l’en-tête CSP appliqué, pas une politique Report-Only, ne juge pas si elle convient à votre site, et ce n’est pas un test d’intrusion.

1. Ce que fait une Content-Security-Policy, et ce qu’elle ne fait pas

Une politique est une liste de directives séparées par des points-virgules. Chaque directive désigne un type de ressource et les sources autorisées pour celui-ci : script-src pour JavaScript, style-src pour CSS, img-src, font-src, connect-src pour les connexions fetch, XHR et WebSocket, et frame-src pour les cadres que la page intègre. default-src sert de valeur de repli à ces directives de récupération lorsqu’une directive précise manque (W3C CSP Level 3).

Trois directives importantes ne se replient pas sur default-src : frame-ancestors (quels sites peuvent afficher votre page dans un cadre), base-uri (ce qu’un élément <base> peut définir) et form-action (vers où les formulaires peuvent être envoyés). Une politique limitée à default-src 'self' laisse donc n’importe quel site encadrer la page et une balise <base> injectée détourner les URL relatives.

Les valeurs de source sont de trois sortes :

  • Des mots-clés entre apostrophes droites : 'self' (l’origine de la page elle-même), 'none' (rien), 'unsafe-inline', 'unsafe-eval' et 'strict-dynamic'.
  • Des hôtes et des schémas : https://cdn.example.com, https://*.example.com, https: ou data:.
  • Des nonces et des hashes : 'nonce-…' et 'sha256-…', qui autorisent des éléments script ou style précis plutôt que des hôtes entiers.

Envoyez la politique sous forme d’en-tête HTTP. Une balise <meta http-equiv="Content-Security-Policy"> fonctionne pour la plupart des directives, mais la spécification y exclut frame-ancestors, report-uri et sandbox, et une politique Report-Only ne peut pas du tout passer par une balise meta (guide CSP de MDN). Gardez des attentes réalistes : une CSP limite ce que du code injecté peut faire. Elle ne remplace ni l’échappement des sorties ni la validation des entrées, qui empêchent l’injection elle-même.

2. Trois exemples de Content-Security-Policy pour démarrer

Exemple A : un site sans scripts tiers. Une politique par liste d’autorisation fonctionne quand chaque script et chaque feuille de style est un fichier servi par votre propre origine :

Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests

Elle bloque tout <script> en ligne, tout attribut style et tout ce qui vient d’autres hôtes ; elle convient donc aux sites faits main ou générés statiquement, sans contenu intégré.

Exemple B : un site rendu côté serveur, avec nonce. C’est la politique stricte que web.dev recommande pour les pages que le serveur génère à chaque requête, complétée par une protection contre l’encadrement :

Content-Security-Policy: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'

Le nonce change à chaque réponse (section 3). Notez ce qui manque : sans default-src, styles, images, polices et connexions ne sont pas restreints. C’est un compromis assumé : la politique se concentre sur l’exécution des scripts, là où le cross-site scripting fait le plus de dégâts, et évite les listes d’hôtes qui cassent chaque fois qu’un fournisseur change de domaine.

Exemple C : des pages statiques ou en cache, avec hashes. Quand tout le monde reçoit le même HTML, autorisez les scripts en ligne par leur hash :

Content-Security-Policy: script-src 'sha256-BASE64_HASH_OF_INLINE_LOADER' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'
Type de politiqueAdaptée àTravail courantPoint de vigilance
A : liste d’hôtesSites statiques sans code tiersMettre à jour la liste à chaque nouveau fournisseurTout script d’un hôte autorisé peut être chargé, y compris d’anciennes versions de bibliothèques sur un CDN partagé
B : nonce + strict-dynamicPages que votre application génère à chaque requêteAjouter le nonce à chaque balise script dans les templatesLes caches de pages qui servent le même nonce à tous les visiteurs
C : hash + strict-dynamicHTML statique, prérendu ou mis en cache en périphérieRecalculer les hashes dès que le code en ligne changeLes minificateurs et templates qui modifient les espaces, donc le hash

Quel que soit votre choix, ajoutez les directives de rapport de la section 6 et envoyez d’abord la politique en Content-Security-Policy-Report-Only.

3. Des nonces et des hashes plutôt que 'unsafe-inline'

'unsafe-inline' autorise tous les scripts en ligne, y compris celui qu’un attaquant aurait injecté, ce qui retire l’essentiel de ce que la CSP apporte contre le cross-site scripting. Les nonces et les hashes n’autorisent que le code en ligne que vous avez réellement prévu de livrer. Les navigateurs ignorent 'unsafe-inline' dans une directive qui contient aussi un nonce ou un hash (MDN) : il peut donc rester dans une politique stricte comme repli pour les très anciens navigateurs.

Les nonces

Un nonce est une valeur aléatoire que le serveur génère pour chaque réponse, place dans l’en-tête et recopie dans l’attribut nonce de chaque script qu’il veut exécuter. Il doit être imprévisible et renouvelé à chaque réponse ; web.dev recommande au moins 128 bits issus d’un générateur cryptographiquement sûr. Dans une application Node.js :

import crypto from 'node:crypto';

app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.nonce = nonce;
  res.setHeader('Content-Security-Policy',
    `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'`);
  next();
});

Le template écrit ensuite <script nonce="…" src="/app.js"></script> avec la même valeur. Ajoutez le nonce là où vos templates produisent vos propres balises script, jamais en réécrivant toutes les balises <script> du HTML final : un remplacement global validerait aussi un script injecté. Deux pièges reviennent souvent : un CDN ou un cache de pages qui stocke le HTML resservira le même nonce à tous les visiteurs, et une valeur figée dans un fichier statique au moment du build n’est pas un nonce.

Les hashes

Un hash autorise un seul script en ligne, à l’identique : l’empreinte SHA-256, SHA-384 ou SHA-512, encodée en base64, du texte compris entre les balises script. Chaque caractère compte, espaces et retours à la ligne compris, si bien qu’une modification du minificateur ou du template produit un nouveau hash. Les hashes conviennent donc mieux aux pages statiques et mises en cache, dont le contenu est le même pour tous. Chrome affiche dans la console le hash attendu lorsqu’il bloque un script en ligne ; vous pouvez aussi le calculer vous-même :

printf '%s' 'console.log("hello")' | openssl dgst -sha256 -binary | openssl base64

Ce que ni l’un ni l’autre n’autorise

Les gestionnaires d’événements en ligne comme onclick="…" et les URL javascript: sont bloqués par une politique à base de nonces ou de hashes. Déplacez ce code dans des fichiers de script et attachez-le avec addEventListener. 'unsafe-hashes' peut autoriser un gestionnaire précis par son hash, mais ce n’est qu’une solution provisoire. Le code qui exécute des chaînes comme du code, par exemple eval(), new Function() ou setTimeout avec une chaîne, exige 'unsafe-eval' ; remplacez-le quand c’est possible et utilisez 'wasm-unsafe-eval', plus restreint, si seul WebAssembly doit être compilé (MDN script-src).

4. strict-dynamic : laisser les scripts de confiance charger ce dont ils ont besoin

La plupart des sites exécutent des scripts qui en chargent d’autres : un gestionnaire de balises ajoute l’analytics, un bandeau de consentement ajoute ses fournisseurs, un widget de chat charge son propre paquet. Lister tous les hôtes qu’ils pourraient utiliser est fragile. 'strict-dynamic' prend un autre chemin : un script autorisé par nonce ou par hash peut ajouter d’autres scripts, qui sont eux aussi considérés comme fiables.

Dans les navigateurs qui le prennent en charge, 'strict-dynamic' fait en outre ignorer les listes d’hôtes, 'self' et 'unsafe-inline' dans script-src (MDN). Trois conséquences en découlent :

  • Chaque balise <script> de votre HTML a besoin du nonce ou d’un hash correspondant, y compris les scripts de votre propre origine, puisque 'self' ne les autorise plus.
  • Les scripts qu’un script de confiance crée avec document.createElement('script') sont autorisés. Ceux qui sont écrits dans la page avec document.write (scripts insérés par l’analyseur) ne le sont pas, et les anciens extraits d’intégration qui en dépendent cessent de fonctionner.
  • La confiance se transmet. Tout ce que charge votre gestionnaire de balises s’exécutera : quiconque peut y publier peut, dans les faits, exécuter du code sur votre site. Traitez cet accès comme un accès au déploiement.

Pour les anciens navigateurs, ajoutez des valeurs de repli que les navigateurs récents ignorent :

script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none'

Un navigateur qui prend en charge CSP Level 3 n’applique que le nonce et 'strict-dynamic'. Un navigateur qui connaît les nonces mais pas 'strict-dynamic' utilise le nonce et https:, et un très ancien navigateur se rabat sur 'unsafe-inline' https:. Google documente un extrait Tag Manager compatible avec les nonces, qui transmet le nonce aux scripts qu’il ajoute, et précise que les variables JavaScript personnalisées de Tag Manager exigent toujours 'unsafe-eval' (guide CSP de Tag Manager).

5. frame-ancestors, object-src 'none' et base-uri : les directives à écrire explicitement

  • frame-ancestors décide quels sites peuvent intégrer votre page dans un cadre ; c’est la parade au clickjacking. Utilisez 'none' si personne n’a besoin d’encadrer la page, 'self' si seules vos propres pages le font, ou listez des origines exactes comme https://app.example.com. La directive ne fonctionne que dans l’en-tête HTTP. X-Frame-Options (DENY ou SAMEORIGIN) reste un repli pour les anciens navigateurs ; faites en sorte que les deux autorisent le même encadrement (MDN frame-ancestors).
  • object-src 'none' bloque <object> et <embed>. Les plugins ont disparu des navigateurs modernes, mais ces éléments peuvent encore charger du contenu, et une politique stricte sans default-src les laisse libres si vous ne dites rien.
  • base-uri 'none', ou 'self' si vos pages utilisent un élément <base>, empêche une balise <base href> injectée de rediriger toutes les URL relatives de scripts vers un autre hôte.
  • form-action 'self' limite la destination des formulaires. Ajoutez l’origine de votre prestataire de paiement ou de connexion si un formulaire y envoie des données, puis testez ces parcours après le changement.
  • upgrade-insecure-requests fait charger en HTTPS les sous-ressources http://. Cela aide contre le contenu mixte, sans remplacer HSTS.

Inscrivez ces directives explicitement dans chaque politique, y compris les exemples B et C. Elles ne demandent presque aucune maintenance et comblent des failles que les règles sur les scripts laissent ouvertes.

6. Les rapports de violation avec report-to et report-uri

Les rapports de violation indiquent ce qu’une politique a bloqué, ou bloquerait en mode Report-Only, dans les navigateurs de vrais visiteurs. CSP Level 3 définit report-to, qui désigne un point de collecte déclaré dans l’en-tête Reporting-Endpoints, et rend obsolète l’ancien report-uri, qui reçoit directement une URL. Selon MDN, report-to est disponible dans les navigateurs actuels depuis 2026, tandis que les versions plus anciennes ne comprennent que report-uri. Les navigateurs qui prennent en charge report-to ignorent report-uri : envoyez donc les deux pour l’instant (MDN report-to) :

Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' 'report-sample'; object-src 'none'; base-uri 'none'; report-uri https://example.com/csp-reports; report-to csp

Les deux mécanismes envoient des formats différents. report-uri transmet un objet JSON avec une clé csp-report et le type de contenu application/csp-report. report-to transmet du application/reports+json : une liste de rapports dont le type vaut csp-violation et dont le corps contient des champs comme documentURL, blockedURL, effectiveDirective et disposition (enforce ou report). Avec 'report-sample', le rapport sur un code en ligne bloqué en inclut les 40 premiers caractères. Acceptez les deux formats sur le même point de collecte.

  • Attendez-vous à du bruit. Les extensions de navigateur injectent des scripts et déclenchent des violations dont le fichier source commence par chrome-extension:// ou moz-extension://. Regroupez les rapports par directive et par hôte bloqué, et cherchez des tendances plutôt que des cas isolés.
  • Traitez les rapports comme des données personnelles. Les URL de document et les referrers peuvent contenir des paramètres avec des adresses e-mail ou des jetons. Supprimez-les ou tronquez-les, conservez les rapports peu de temps et ne les transmettez pas sans raison.
  • Protégez le point de collecte. N’importe qui peut envoyer de faux rapports : appliquez des limites de taille et de débit, et n’affichez jamais leur contenu sans échappement dans une interface d’administration.

7. Ce qui casse le plus souvent : analytics, polices, scripts en ligne et intégrations

La plupart des violations des premiers jours viennent d’un petit nombre de sources. Le champ effectiveDirective du rapport vous indique quelle règle examiner :

SymptômeDirective dans le rapportCorrection habituelle
L’analytics n’enregistre plus les pages vuesscript-src-elem, connect-src, img-srcCharger la balise avec un nonce, ou autoriser son hôte de script, et autoriser les hôtes de collecte documentés par le fournisseur (Google les publie par produit)
Les polices web sont remplacées par celles du systèmefont-src, style-src-elemAutoriser l’hôte de la feuille de style dans style-src et celui des fichiers de police dans font-src (pour Google Fonts, fonts.googleapis.com et fonts.gstatic.com), ou héberger les fichiers vous-même
Un extrait du thème ou du CMS ne fonctionne plusscript-src-elem avec blockedURL « inline »Ajouter le nonce dans le template, déplacer le code dans un fichier ou autoriser son hash
Les boutons avec onclick ne font rienscript-src-attrRéécrire le gestionnaire avec addEventListener dans un fichier de script
Les variables personnalisées du gestionnaire de balises ne renvoient rienscript-src (eval)Les remplacer par des variables intégrées, ou accepter 'unsafe-eval' comme exception consignée
Une vidéo, une carte ou un cadre de paiement intégré reste videframe-srcAjouter l’origine exacte du fournisseur
Les styles en ligne sont ignorés et la mise en page se décalestyle-src-elem, style-src-attrDéplacer les styles dans des feuilles de style, ajouter le nonce aux éléments style, ou garder 'unsafe-inline' dans style-src uniquement, comme exception consignée
Le chat ou les mises à jour en direct ne se connectent pasconnect-srcAjouter les hôtes https:// et wss:// du fournisseur
Votre application ou un partenaire ne peut plus intégrer la pageframe-ancestorsLister les origines exactes qui l’intègrent

Les styles en ligne présentent moins de risques que les scripts en ligne, sans être anodins : du CSS injecté peut modifier ce que voient les visiteurs et, au moyen de sélecteurs d’attributs, laisser fuiter certaines données de la page. Beaucoup de frameworks et de bibliothèques CSS-in-JS insèrent des éléments style à l’exécution ; vérifiez que le vôtre prend en charge les nonces avant de retirer 'unsafe-inline' de style-src. Héberger vous-même polices et bibliothèques fait disparaître plusieurs de ces lignes d’un coup et garde la politique courte.

8. Déployer une CSP par étapes

  1. Inventaire. Recensez les scripts, styles, polices, cadres et destinations de connexion de vos pages clés : accueil, connexion, compte client, paiement et pages avec widgets intégrés. Le panneau Réseau des DevTools et la configuration de votre gestionnaire de balises sont les sources les plus rapides.
  2. Report-Only. Envoyez le brouillon en Content-Security-Policy-Report-Only avec les rapports activés. Rien n’est bloqué ; les violations apparaissent dans la console et sur votre point de collecte. Laissez tourner assez longtemps pour voir apparaître les pages peu visitées et les campagnes, en général une semaine ou plus.
  3. Corriger les sources, pas la politique. Ajoutez des nonces, sortez les gestionnaires en ligne dans des fichiers, hébergez les polices vous-même. N’ajoutez un hôte ou 'unsafe-eval' que comme exception consignée, avec un responsable et une justification.
  4. Appliquer. Remplacez le nom de l’en-tête par Content-Security-Policy. Gardez à côté une politique Report-Only plus stricte pour tester le resserrement suivant : quand les deux en-têtes arrivent, le navigateur applique l’un et se contente de signaler pour l’autre.
  5. Rester attentif. Une nouvelle balise marketing, une mise à jour d’extension ou de framework peut introduire du code en ligne. Laissez le point de collecte actif et revérifiez l’en-tête après chaque changement de serveur, de CDN ou de framework.

Deux détails d’infrastructure causent bien des surprises. Si un CDN ou un framework ajoute son propre en-tête CSP, le navigateur applique toutes les politiques reçues et une ressource doit les satisfaire toutes : un en-tête supplémentaire ne peut que durcir, jamais assouplir (MDN). Et si le HTML est mis en cache en périphérie, une politique à nonce suppose de générer le nonce là où la page est assemblée, ou de passer aux hashes. Pour voir ce qu’une page envoie réellement :

curl -sS -D - -o /dev/null https://example.com/ | grep -i content-security-policy

Le guide de vérification des en-têtes de sécurité couvre les en-têtes qui accompagnent la CSP, et la checklist de sécurité avant la mise en ligne les replace dans une revue complète avant le lancement.

9. Contrôler l’en-tête en production avec le vérificateur gratuit

Une fois la politique appliquée, le vérificateur d’en-têtes de sécurité gratuit montre ce qu’un navigateur reçoit de votre page d’accueil. Il lance le snapshot gratuit de préparation au lancement de Sitelemetry, huit contrôles passifs sur un domaine public sans inscription, et affiche le contrôle des en-têtes, déplié, au-dessus des sept autres.

  • Il demande la page d’accueil de l’origine saisie, suit jusqu’à cinq redirections et lit la réponse finale. Les autres pages, comme la connexion ou le paiement, portent souvent des politiques différentes ; vérifiez-les avec les DevTools ou curl.
  • Il ne lit que l’en-tête Content-Security-Policy appliqué. Pendant la phase Report-Only, il signale donc toujours la CSP comme absente, avec un risque moyen, puisqu’une politique de simple rapport ne bloque rien. Ce résultat est normal tant que vous n’appliquez pas la politique.
  • Une directive frame-ancestors dans la politique appliquée compte comme protection contre l’encadrement, au même titre que X-Frame-Options.
  • Dans la note de durcissement, distincte (de A à F), la CSP rapporte moins de points quand default-src ou une directive script-src ou style-src (y compris leurs variantes -elem et -attr) contient 'unsafe-inline', ou quand default-src ou une directive de script contient 'unsafe-eval'. La note lit la politique telle qu’elle est écrite : un 'unsafe-inline' de repli placé à côté d’un nonce fait donc aussi baisser le score, même si les navigateurs actuels l’ignorent.
  • Il n’évalue ni les nonces, ni les hashes, ni 'strict-dynamic', object-src, base-uri ou les rapports. Pour cela, servez-vous de la console du navigateur et de vos rapports de violation.

Le résultat affiche un score et le résultat de chacun des huit contrôles : le signal CSP apparaît ainsi à côté de HSTS, de TLS et des autres contrôles publics.

Questions fréquentes

Quel exemple de Content-Security-Policy choisir pour commencer ?

Pour un site rendu côté serveur : script-src 'nonce-{aléatoire}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self', avec un nouveau nonce à chaque réponse. Pour des pages statiques ou en cache, utilisez des hashes de vos scripts en ligne à la place du nonce. Démarrez l’une ou l’autre en Content-Security-Policy-Report-Only.

Faut-il un nonce ou un hash dans ma CSP ?

Un nonce quand votre application génère chaque page, car elle peut insérer une nouvelle valeur à chaque fois. Des hashes quand le HTML est statique ou en cache, car un nonce mis en cache serait réutilisé pour tous les visiteurs. Les deux se combinent avec 'strict-dynamic'.

Est-ce grave d’avoir 'unsafe-inline' dans style-src ?

C’est un risque moindre que 'unsafe-inline' dans script-src, mais du CSS injecté peut encore modifier ce que voient les visiteurs et laisser fuiter certaines données de la page. Ne le gardez que comme exception consignée, le temps de déplacer les styles dans des feuilles de style ou d’ajouter des nonces aux éléments style.

Content-Security-Policy-Report-Only protège-t-il mon site ?

Non. Il ne bloque rien ; il signale seulement ce qu’une politique appliquée bloquerait. Servez-vous-en pour tester, puis passez à l’en-tête Content-Security-Policy. Les deux peuvent être envoyés ensemble, ce qui est pratique pour tester la version suivante, plus stricte.

Peut-on définir une CSP dans une balise meta ?

Oui, pour la plupart des directives. Mais frame-ancestors, report-uri et sandbox sont ignorés dans une balise meta, et une politique Report-Only ne peut pas y être déclarée. Utilisez l’en-tête HTTP dès que votre serveur ou votre hébergeur le permet.

Quelle différence entre report-uri et report-to ?

report-uri reçoit directement une URL ; CSP Level 3 le déclare obsolète, mais les anciens navigateurs en dépendent encore. report-to désigne un point de collecte défini dans l’en-tête Reporting-Endpoints et utilise le format de la Reporting API. Les navigateurs qui gèrent report-to ignorent report-uri : envoyer les deux ne pose donc aucun problème.

Sources et lectures

  1. W3C : Content Security Policy Level 3www.w3.org
  2. MDN : guide de la Content Security Policy (CSP)developer.mozilla.org
  3. MDN : en-tête Content-Security-Policydeveloper.mozilla.org
  4. MDN : directive CSP script-srcdeveloper.mozilla.org
  5. MDN : directive CSP frame-ancestorsdeveloper.mozilla.org
  6. MDN : directive CSP report-todeveloper.mozilla.org
  7. MDN : en-tête Reporting-Endpointsdeveloper.mozilla.org
  8. W3C : Reporting APIwww.w3.org
  9. web.dev : Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP)web.dev
  10. Google Tag Platform : Use Tag Manager with a Content Security Policydevelopers.google.com
  11. OWASP Content Security Policy Cheat Sheetcheatsheetseries.owasp.org
Équipe Sitelemetry

Préparé par la rédaction de Sitelemetry. Les sources citées permettent d’approfondir chaque sujet.

SITELEMETRY

Mettez vos connaissances en pratique.

Explorez la structure, les réglages et les signaux de votre site avec Sitelemetry.