La Content Security Policy est l'un des mécanismes de sécurité les plus puissants disponibles dans les navigateurs modernes, et l'un des plus mal compris. Une CSP bien configurée rend les attaques XSS (Cross-Site Scripting) quasi impossibles, même si votre code contient des vulnérabilités. Une CSP mal configurée bloque vos propres scripts et brise votre site sans rien protéger.
Ce guide vous amène de zéro à une CSP fonctionnelle et efficace.
Pourquoi la CSP Existe
Le web a été conçu avec un modèle de confiance implicite : tout script chargé sur votre page a les mêmes droits que votre propre code. Cette conception, acceptable dans les années 1990, est devenue un problème majeur.
Sans CSP, si un attaquant injecte du JavaScript dans votre page (via une faille XSS, une librairie compromise, ou un commentaire utilisateur non filtré), ce script peut :
- Voler les cookies de session et les tokens d'authentification
- Exfiltrer les données des formulaires vers un serveur externe
- Afficher de faux formulaires de paiement (formjacking)
- Miner des cryptomonnaies en arrière-plan
La CSP résout ce problème en indiquant au navigateur exactement quelles sources sont autorisées pour chaque type de ressource. Tout le reste est bloqué.
Syntaxe des Directives
Une CSP est un header HTTP avec une valeur textuelle structurée :
Content-Security-Policy: directive1 source1 source2; directive2 source3;
Directives essentielles
| Directive | Contrôle |
|-----------|----------|
| default-src | Fallback pour toutes les ressources non spécifiées |
| script-src | Scripts JavaScript |
| style-src | Feuilles de style CSS |
| img-src | Images |
| connect-src | Requêtes XHR, fetch, WebSockets |
| font-src | Polices de caractères |
| frame-src | Iframes |
| object-src | Plugins (Flash, etc.) |
| form-action | Destinations des soumissions de formulaires |
Sources disponibles
| Source | Signification |
|--------|---------------|
| 'self' | Le même domaine que la page |
| 'none' | Rien n'est autorisé |
| https: | Toutes les URLs HTTPS |
| https://cdn.example.com | Ce domaine spécifique |
| *.example.com | Tous les sous-domaines |
| 'unsafe-inline' | Styles/scripts inline (déconseillé) |
| 'unsafe-eval' | Fonctions d'évaluation dynamique (déconseillé) |
| 'nonce-{value}' | Scripts avec l'attribut nonce correspondant |
| 'strict-dynamic' | Fait confiance aux scripts approuvés pour charger d'autres scripts |
Exemples Progressifs
CSP basique (point de départ)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none';
Cette politique autorise uniquement les ressources de votre propre domaine. Elle est sécurisée mais bloquera la plupart des sites qui utilisent des CDN, Google Fonts ou Analytics.
CSP pour un site classique avec CDN
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net https://www.google-analytics.com;
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https://www.google-analytics.com;
connect-src 'self' https://www.google-analytics.com;
object-src 'none';
frame-ancestors 'none';
CSP stricte avec nonce (recommandée)
La technique du nonce permet d'autoriser des scripts inline spécifiques sans 'unsafe-inline'. Le serveur génère un identifiant aléatoire unique à chaque requête :
<!-- Le serveur génère un nonce unique à chaque requête -->
<script nonce="r4nd0m-v4lu3-ch4ng3-each-r3qu3st">
// Ce script est autorisé car il possède le bon nonce
initializeApp();
</script>
Content-Security-Policy: script-src 'nonce-r4nd0m-v4lu3-ch4ng3-each-r3qu3st' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Implémentation en Node.js/Express :
import crypto from 'crypto'
app.use((req, res, next) => {
// Générer un nonce cryptographiquement sécurisé à chaque requête
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';`
)
next()
})
Implémentation en Next.js (middleware) :
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import crypto from 'crypto'
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
const response = NextResponse.next({ request: { headers: requestHeaders } })
response.headers.set('Content-Security-Policy', cspHeader)
return response
}
Erreurs Courantes
1. Utiliser 'unsafe-inline' pour les scripts
# MAUVAIS — annule toute protection XSS pour les scripts
script-src 'self' 'unsafe-inline';
# BON — utiliser des nonces ou des hashes
script-src 'self' 'nonce-{generated-nonce}';
'unsafe-inline' pour script-src annule pratiquement toute protection de la CSP. Si vous en avez besoin à court terme, utilisez Content-Security-Policy-Report-Only d'abord pour mesurer l'impact réel avant de durcir.
Note : 'unsafe-inline' est acceptable pour style-src car les injections CSS sont moins critiques que les injections JavaScript.
2. Oublier object-src
Si vous n'incluez pas object-src 'none', les éléments <object>, <embed> et <applet> héritent de default-src. Ces éléments sont des vecteurs d'attaque historiques qui ne sont presque jamais utilisés légitimement dans les applications modernes.
3. Wildcard trop large
# MAUVAIS — autorise n'importe quel script HTTPS
script-src https:;
# BON — lister explicitement les domaines autorisés
script-src 'self' https://cdn.votre-librairie.com;
4. Oublier les sous-domaines
Si votre CDN est sur static.exemple.com, vous devez l'ajouter explicitement à la liste des sources. 'self' ne couvre que le domaine exact, pas les sous-domaines.
Déployer en Mode Report-Only
Avant d'activer une CSP en production, utilisez le mode Report-Only pour collecter les violations sans rien bloquer :
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /api/csp-report
// Endpoint de collecte des rapports
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
console.error('CSP Violation:', req.body['csp-report'])
// Envoyer à votre système de monitoring
res.status(204).send()
})
Après quelques jours de collecte, analysez les violations pour ajuster votre politique avant de passer en mode bloquant.
Déboguer avec les DevTools
Chrome et Firefox affichent les violations CSP dans la console développeur (F12 > Console). Chaque violation indique :
- La ressource bloquée
- La directive violée
- La source de la ressource
Refused to load the script 'https://external-cdn.com/script.js' because it violates
the following Content Security Policy directive: "script-src 'self'".
Pour voir toutes les violations en temps réel pendant le développement, utilisez l'onglet "Network" filtré sur "blocked" ou l'extension "CSP Evaluator" de Google.
Vérifier Votre CSP avec WarDek
Écrire une CSP manuellement est une tâche complexe et sujette aux erreurs. Un header trop permissif ne protège rien ; un header trop restrictif casse votre site.
WarDek analyse automatiquement votre Content Security Policy et identifie :
- Les directives manquantes (notamment
object-srcetbase-uri) - Les sources trop permissives (wildcards,
'unsafe-inline'sur les scripts) - Les domaines autorisés qui ne servent plus de ressources actives
- La compatibilité avec les navigateurs cibles
Combinez notre vérification CSP avec notre guide complet des headers HTTP et notre scanner de protection XSS pour une couverture de sécurité complète.
Une CSP correctement configurée est l'une des mesures de sécurité les plus efficaces que vous puissiez mettre en place. Elle transforme une vulnérabilité XSS critique en un incident mineur contenu.