Construire une CSP solide, étape par étape
CentralCSP Team ·
Dernière mise à jour:
Une politique de sécurité du contenu (CSP) est un en-tête de réponse HTTP qui indique au navigateur quels scripts, styles et autres ressources une page est autorisée à charger et exécuter. C'est la défense la plus efficace dont vous disposiez dans le navigateur contre le cross-site scripting (XSS) et le vol de données par script. Le plus difficile n'est pas la syntaxe, c'est d'écrire une politique qui verrouille la page sans la casser.
La méthode fiable tient en deux temps : mesurer d'abord, appliquer ensuite. Vous partez d'une politique stricte en mode report-only, vous observez ce que le trafic réel aurait enfreint, vous construisez la vraie politique à partir de ces preuves, vous la validez, puis vous activez l'application et continuez à surveiller. Cet article déroule ce flux de bout en bout, avec l'en-tête de départ exact et la configuration par framework.
Vous débutez avec la CSP ? Commencez par démarrer avec la politique de sécurité du contenu pour les bases, ce qu'est la CSP, votre première politique et votre premier nonce, puis revenez ici pour la construction complète.
Le flux en une minute
Voici le processus complet, avant d'entrer dans le détail :
- Démarrez en Report-Only. Déployez une politique délibérément stricte sur l'en-tête
Content-Security-Policy-Report-Only. Le navigateur ne bloque rien et signale tout ce qu'il aurait bloqué. - Posez l'en-tête sur votre serveur ou votre framework. Utilisez le mécanisme prévu par votre stack, pour que l'en-tête parte sur chaque réponse.
- Collectez les rapports de violation depuis le trafic réel. Pointez la politique vers un endpoint de reporting et laissez les vrais utilisateurs générer les données.
- Construisez la vraie politique à partir de ce que vous voyez. Chaque rapport vous indique une origine ou une ressource inline dont votre page a réellement besoin. Ajoutez le minimum pour l'autoriser.
- Validez. Notez la politique, confirmez qu'elle n'a pas de contournement évident et vérifiez qu'elle est vraiment stricte.
- Appliquez. Faites passer la politique validée de
Content-Security-Policy-Report-OnlyàContent-Security-Policy. - Continuez à surveiller. Le code que vous ajoutez et les tiers changent ce que la page charge. Gardez le flux de rapports ouvert pour repérer les ruptures et les altérations.
Les mêmes sept étapes sous forme de flux :
Le principe derrière chaque étape : n'appliquez jamais une politique que vous n'avez pas mesurée contre le trafic réel. Report-Only est ce qui rend cela sûr.
Étape 1 : commencez par une politique Report-Only stricte
Commencez par une politique volontairement trop stricte. En mode report-only, le navigateur n'applique rien : une politique trop serrée ne vous coûte donc rien, sinon un flux de rapports qui vous disent exactement ce dont la page dépend. Ce sont précisément les données que vous cherchez.
Envoyez ceci sur l'en-tête Content-Security-Policy-Report-Only comme point de départ :
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'report-sample';
style-src 'self';
img-src 'self';
font-src 'self';
object-src 'none';
base-uri 'none';
form-action 'none';
frame-ancestors 'none';
frame-src 'self';
connect-src 'none';
upgrade-insecure-requests;
report-uri https://<Endpoint-ID>.report.centralcsp.com;
report-to csp-endpointCe que fait chaque partie :
default-src 'self'sert de repli à toute directive de fetch que vous ne nommez pas : tout ce qui n'est pas listé se limite donc à la même origine.script-src 'self' 'report-sample'autorise les scripts de même origine.'report-sample'indique au navigateur d'inclure un court échantillon, les 40 premiers caractères, de tout code inline fautif dans le rapport, ce qui rend les violations bien plus faciles à identifier. Il est accepté dansscript-src,style-srcet leurs variantes-elemet-attr.object-src 'none'bloque les plugins.base-uri 'none'bloque l'injection d'une balise<base>, qui pourrait sinon détourner les URL relatives et déjouer une politique fondée sur les nonces.form-action 'none'interdit l'envoi des formulaires, quelle que soit la destination.frame-ancestors 'none'est une protection anti-clickjacking et le remplacement moderne deX-Frame-Options; si vous ne pouvez pas définir de header CSP du tout, voyez la protection contre le clickjacking quand vous ne pouvez pas définir de header CSP.frame-src 'self'limite ce que la page peut encadrer.upgrade-insecure-requestsmet à niveau les requêtes de sous-ressources non sécurisées vers HTTPS avant qu'elles n'atteignent le réseau, sans repli HTTP. Elle ne prend aucune valeur. Elle ne met pas à niveau les navigations de premier niveau cross-origin et elle ne remplace pas HSTS ; voyez HSTS vs upgrade-insecure-requests pour savoir en quoi les deux diffèrent.report-urietreport-toenvoient les rapports de violation à votre collecteur. Plus de détails sur ces deux directives à l'étape 3.
Deux pièges que cette politique est conçue pour révéler
Ce point de départ n'est volontairement pas applicable tel quel. Deux directives vont générer beaucoup de rapports, et c'est justement le but.
connect-src 'none' bloque chaque requête fetch, XMLHttpRequest, WebSocket et EventSource. Cela cassera presque tout site réel dès que vous l'appliquerez. Partir de 'none' oblige chaque appel réseau de votre page à se signaler dans un rapport, et vous les voyez donc tous. Presque toutes les applications finissent par avoir besoin au moins de connect-src 'self' plus leurs origines d'API et d'analytics.
style-src 'self' fera remonter les styles inline, que des frameworks comme Angular et les bibliothèques CSS-in-JS injectent en permanence. Vous réglez ça avec un nonce de style, plutôt qu'en ouvrant la politique avec 'unsafe-inline'.
La politique de départ est un instrument de mesure, pas votre politique finale. Attendez-vous à beaucoup de rapports le premier jour. C'est le flux qui fait son travail.
Étape 2 : posez l'en-tête CSP sur votre serveur ou votre framework
Une politique ne sert à rien si l'en-tête n'atteint pas le navigateur sur chaque réponse, pages d'erreur comprises. Utilisez le mécanisme prévu par votre stack. Chaque exemple ci-dessous pose un en-tête CSP en mode application ou report-only ; remplacez le nom de l'en-tête et les directives par la politique de départ de l'étape 1. Pour plus de stacks et des snippets complets, voyez comment définir le header CSP dans chaque framework.
Nginx
Avec le ngx_http_headers_module, utilisez add_header avec always pour que l'en-tête soit envoyé aussi sur les réponses d'erreur :
add_header Content-Security-Policy "default-src 'self'" always;Apache
Avec mod_headers :
Header always set Content-Security-Policy "default-src 'self'"Traefik
Avec le middleware headers, posez un en-tête de réponse personnalisé :
traefik.http.middlewares.csp.headers.customresponseheaders.Content-Security-Policy=default-src 'self'Express (Helmet)
Helmet pose l'en-tête pour vous et prend directement en charge le mode report-only. Les valeurs de directive acceptent des fonctions, et c'est comme ça qu'on fournit un nonce neuf à chaque requête :
app.use(
helmet({
contentSecurityPolicy: {
directives: { "script-src": ["'self'"] },
reportOnly: true,
},
})
);Django (django-csp)
Ajoutez csp.middleware.CSPMiddleware, puis définissez la politique dans les settings. Utilisez CONTENT_SECURITY_POLICY_REPORT_ONLY pour la phase report-only et CONTENT_SECURITY_POLICY une fois que vous appliquez :
CONTENT_SECURITY_POLICY_REPORT_ONLY = {
"DIRECTIVES": {
"default-src": ["'self'"],
"script-src": ["'self'", "'report-sample'"],
},
}Next.js (App Router)
Pour une politique stricte fondée sur les nonces avec un rendu dynamique, générez un nonce par requête dans proxy.ts et émettez script-src 'self' 'nonce-...' 'strict-dynamic'. Le mot-clé 'strict-dynamic' laisse les scripts de confiance en charger d'autres, sans allowlist d'hôtes. Pour une politique statique, renvoyez l'en-tête depuis async headers() dans next.config.js. C'est la route proxy qui vous permet de servir un vrai nonce ; pour la configuration complète, voyez comment configurer un nonce CSP avec Next.js.
Nuxt (nuxt-security)
Configurez security.headers.contentSecurityPolicy dans nuxt.config.ts. Sa valeur par défaut est déjà une politique à nonce strict avec 'strict-dynamic' : vous resserrez à partir d'une bonne base au lieu de partir de zéro.
Laravel (spatie/laravel-csp)
Enregistrez le middleware Spatie\Csp\AddCspHeaders::class, puis définissez votre politique via des presets dans config/csp.php. Le package fournit des presets report-only pour la phase de collecte.
Angular
Angular ne pose pas l'en-tête lui-même : c'est à votre serveur de le faire, avec l'un des mécanismes ci-dessus. Ce qu'Angular apporte, c'est un nonce par requête, fourni via le token CSP_NONCE ou l'attribut ngCspNonce. Associez-le à un script-src 'self' 'nonce-...' posé côté serveur.
Étape 3 : collectez les rapports de violation depuis le trafic réel
La politique de départ pointe déjà vers un endpoint de reporting. De vrais utilisateurs sur de vraies pages génèrent maintenant les preuves à partir desquelles vous construisez la politique. Les tests synthétiques passent à côté de la longue traîne : scripts tiers, analytics régionaux, pages de cas limites. Collectez donc sur le trafic réel.
Envoyez les rapports à votre collecteur
Utilisez la directive report-to avec l'en-tête de réponse Reporting-Endpoints, qui nomme votre collecteur. report-uri est son prédécesseur déprécié ; vous pouvez encore l'envoyer sur la même politique, en pointant vers le même collecteur. Les différences sont dans report-uri vs report-to.
Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'report-sample';
...
report-uri https://<Endpoint-ID>.report.centralcsp.com;
report-to csp-endpointLisez les rapports, ne vous y noyez pas
Le JSON brut des rapports CSP arrive une violation à la fois et devient vite des milliers d'enregistrements quasi identiques. Tout le travail consiste à les regrouper : quelles origines distinctes et quelles ressources inline la page utilise-t-elle vraiment, et lesquelles ressemblent à du bruit ou à une tentative d'injection ?
C'est exactement ce pour quoi CentralCSP est conçu. Il ingère vos rapports de violation Report-Only, les regroupe par directive et par origine, et affiche les scripts qui s'exécutent sur chaque page grâce au reporting de hash CSP : vous voyez donc quoi autoriser, avant d'appliquer. Vous pouvez démarrer un essai gratuit, y pointer un en-tête Report-Only et regarder les rapports arriver du trafic réel. Si vous préférez d'abord auditer une politique existante, le scanner CSP vérifie ce qu'un site en ligne envoie déjà.

Étape 4 : construisez la vraie politique à partir de ce que vous voyez
Transformez maintenant les rapports regroupés en politique. Travaillez directive par directive et tranchez, pour chaque rapport, entre trois issues : la ressource est légitime et vous l'autorisez avec la source la plus étroite possible, vous pouvez la supprimer ou l'héberger vous-même, ou elle est suspecte et vous enquêtez.
Quelques règles empiriques à garder en tête :
- Ajoutez des origines spécifiques, pas des jokers. Si les rapports montrent des appels
connect-srcvers votre API et un hôte d'analytics, listez ces deux origines, pashttps:. - Hébergez vous-même tout ce que vous pouvez raisonnablement héberger. Moins d'origines tierces, c'est une politique plus courte et une surface d'exposition plus réduite sur la chaîne d'approvisionnement.
- Pour les scripts et styles inline dont vous avez vraiment besoin, utilisez un nonce ou un hash, jamais
'unsafe-inline'. Le mot-clé'report-sample'de la politique de départ et l'inventaire de scripts vous indiquent quels blocs inline sont les vôtres.
Rendez script-src vraiment solide
C'est l'étape qui sépare une politique moyenne d'une politique solide, et elle s'applique à la politique appliquée, pas au point de départ du premier jour.
Les listes d'autorisation d'hôtes dans script-src sont faibles. Le moindre endpoint de redirection ouverte ou JSONP sur un hôte autorisé peut servir de contournement. L'approche solide consiste à abandonner les listes d'hôtes pour les scripts et à ne faire confiance qu'aux nonces ou aux hashes, avec 'strict-dynamic' :
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none''strict-dynamic' indique au navigateur d'ignorer les listes d'hôtes, 'self' et 'unsafe-inline' pour les scripts, et de ne faire confiance qu'aux scripts marqués par un nonce ou un hash, ainsi qu'à ceux que ces scripts chargent. C'est la façon moderne d'autoriser des scripts sans listes d'hôtes fragiles.
Quoi que vous fassiez, ne comptez pas sur 'unsafe-inline' pour faire taire les erreurs de script. Cela réautorise exactement l'exécution inline que la CSP est là pour bloquer. Le raisonnement complet est dans pourquoi vous ne devriez jamais utiliser 'unsafe-inline' dans une CSP, et il en va de même pour son cousin : voyez unsafe-eval et comment le supprimer.
Le cas le plus fréquent, ce sont les tag managers tiers. Pour faire tourner GA4 et Google Tag Manager sous un nonce avec 'strict-dynamic', sans lister les hôtes Google dans script-src, voyez CSP avec Google Analytics et Tag Manager.
Pour la liste complète des directives, valeurs et mots-clés au moment d'assembler la politique, voyez la référence des politiques CSP.
Étape 5 : validez la politique
Avant d'appliquer, vérifiez que la politique est stricte et sans contournement évident. Une politique peut être syntaxiquement valide et ne servir à rien en pratique, par exemple un script-src qui retombe sur 'unsafe-inline' ou qui autorise un hôte connu pour héberger des scripts arbitraires.
Passez-la dans l'évaluateur CSP : il note la nouvelle politique et signale les sources faibles, un object-src ou un base-uri manquant, un joker qui ruine tout l'intérêt. Corrigez ce qu'il remonte, puis revérifiez. C'est aussi le moment de vous assurer que vous n'avez pas élargi une directive sans le vouloir en courant après les rapports à l'étape 4.
Étape 6 : appliquez
Quand le flux Report-Only s'est calmé, c'est-à-dire quand les seules violations restantes sont du bruit ou de vraies tentatives que vous avez tout intérêt à bloquer, promouvez la politique. Déplacez exactement les mêmes directives de l'en-tête Content-Security-Policy-Report-Only vers l'en-tête appliqué Content-Security-Policy.
Gardez aussi un report-uri et un report-to sur l'en-tête appliqué. Application et reporting ne s'excluent pas : une politique appliquée envoie quand même un rapport pour chaque ressource qu'elle bloque, et c'est ainsi que vous découvrirez qu'elle casse quelque chose qui vous avait échappé.
Une pratique courante et plus sûre consiste à laisser tourner les deux en-têtes en parallèle pendant un temps : la politique validée en application, et une candidate encore plus stricte en Report-Only, pour continuer à resserrer sans risque.
Étape 7 : continuez à surveiller
Une CSP n'est pas un en-tête que l'on pose et que l'on oublie. Chaque nouvelle fonctionnalité, chaque montée de version d'une dépendance, chaque balise tierce peut amener une origine ou un bloc inline que votre politique n'autorise pas. Résultat : soit la page casse, soit, plus inquiétant, quelque chose a changé sur la page à votre insu.
Gardez le flux de rapports ouvert en production. Deux choses à surveiller :
- Rupture : une nouvelle ressource légitime que votre politique bloque. Le rapport vous dit quoi ajouter.
- Altération : des scripts ou des origines qui apparaissent sans que personne dans votre équipe les ait introduits. Sur une page de paiement, c'est le signal précoce de formjacking ou d'un skimmer de type Magecart.
La surveillance continue des rapports CSP, l'inventaire de scripts avec détection des CVE et les alertes sur les scripts nouveaux ou modifiés forment le cœur de la suite CSP de CentralCSP. Sur les pages de paiement, cette surveillance continue des scripts est ce qui vous aide à répondre aux exigences côté client de PCI DSS v4 (6.4.3 et 11.6.1) ; voyez CSP pour PCI DSS v4 pour savoir comment politique et exigences se recoupent. La plateforme ne certifie pas la conformité : elle fournit la surveillance et des preuves exportables, et c'est votre QSA qui valide.
Foire aux questions
Combien de temps faut-il faire tourner une CSP en Report-Only ?
Assez longtemps pour voir passer le trafic réel sur vos pages et vos tiers, souvent une à deux semaines. Arrêtez quand les seules violations restantes sont du bruit ou de vraies tentatives que vous avez tout intérêt à bloquer.
Qu'est-ce qu'une bonne CSP de départ ?
Une politique Report-Only délibérément stricte : default-src 'self', object-src 'none', base-uri 'none', frame-ancestors 'none', et un script-src que vous resserrez vers les nonces. Utilisez l'en-tête de départ ci-dessus et laissez les rapports vous guider.
Faut-il utiliser report-uri ou report-to ?
Utilisez report-to avec l'en-tête Reporting-Endpoints ; c'est le mécanisme actuel. report-uri est le prédécesseur déprécié, que vous pouvez encore inclure sur la même politique si vous le souhaitez.
Qu'est-ce qui rend script-src solide ?
Des nonces ou des hashes avec 'strict-dynamic', à la place des listes d'autorisation d'hôtes. Un endpoint de redirection ouverte ou JSONP sur un hôte autorisé ne peut alors plus servir de contournement par injection de script.
Un rapide récapitulatif de l'ordre
La force d'une CSP vient de l'ordre des étapes, pas d'une directive en particulier :
- Politique stricte en Report-Only d'abord, pour que la mesure ne coûte rien.
- Collectez sur le trafic réel, parce que les tests synthétiques passent à côté de la longue traîne.
- Construisez au plus juste, un rapport à la fois, avec des nonces et
'strict-dynamic'pour les scripts. - Validez, appliquez, puis continuez à surveiller, parce que la page ne cesse de changer.
Un mot sur la construction de la politique elle-même : un CSP Builder guidé, qui transforme vos rapports collectés en une politique prête à livrer, arrive bientôt, avec son propre guide pratique. En attendant, ce flux vous donne une politique solide, écrite à la main.
Pour aller plus loin : le guide de MDN sur la CSP et la spécification W3C CSP Level 3.