Nonces ou hashes en CSP : lequel choisir ?
Un nonce exige une valeur aléatoire neuve à chaque réponse, un hash exige un contenu de script stable. Quelle stratégie de CSP stricte pour votre stack : applications server-rendered, sites statiques, CDN et les pièges de cache entre les deux.
Nonces et hashes résolvent le même problème : autoriser les scripts inline et dynamiques que vous avez voulu livrer tout en bloquant ce qu’un attaquant injecte, pour enfin supprimer 'unsafe-inline' de votre politique. Ils diffèrent sur un détail opérationnel qui décide de tout le reste. Un nonce doit changer à chaque réponse. Un hash ne doit jamais changer.
Vous avez là la réponse courte. Pages rendues côté serveur, où générer une valeur aléatoire par requête est possible : nonces. Sites statiques, pages agressivement mises en cache, tout ce qui est servi à l’identique à chaque visiteur : hashes. Dans les deux cas, ajoutez 'strict-dynamic' si vos scripts chargent d’autres scripts.
La suite de cet article, c’est le raisonnement, parce que les modes d’échec se cachent dans les détails.
Comment fonctionnent les nonces et où ils cassent
Un nonce est une valeur aléatoire générée à chaque réponse, placée dans l’en-tête et répétée sur chaque tag script légitime :
Content-Security-Policy: script-src 'nonce-8IBTHwOdqNKAWeKl7plt8g'
<script nonce="8IBTHwOdqNKAWeKl7plt8g">/* s’exécute */</script>
<script>/* bloqué : pas de nonce */</script>
Le navigateur n’exécute que les scripts porteurs du nonce courant. L’attaquant qui injecte du markup ne connaît pas la valeur, son script reste donc lettre morte. Pour que cela tienne, le nonce doit provenir d’une source aléatoire cryptographique, faire au moins 128 bits, être encodé en base64 et ne jamais se répéter.
C’est cette dernière exigence qui fait échouer les déploiements réels. Un cache pleine page au CDN fige un nonce dans le HTML mis en cache et le sert à des milliers de visiteurs. La politique « fonctionne » toujours, au sens où les pages s’affichent, mais le nonce est devenu une constante que n’importe quel attaquant peut lire dans le code source et réutiliser. Nous avons vu ce montage précis passer une revue de sécurité parce que personne n’avait regardé deux réponses de suite.
Le prérequis honnête des nonces est donc celui-ci : chaque couche entre votre moteur de templates et le visiteur doit régénérer le nonce ou ne pas mettre le HTML en cache. Les frameworks server-rendered s’en sortent bien (le site principal de CentralCSP propose un guide nonce pour Next.js). Les exports statiques et les pages marketing en cache edge, non.
Comment fonctionnent les hashes et où ils cassent
Une source hash autorise un corps de script exact :
Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='
Tout script inline dont l’empreinte SHA-256 correspond s’exécute, le reste est bloqué. Aucune génération côté serveur, aucune contrainte de cache. L’en-tête est aussi statique que la page, exactement ce qu’un CDN demande.
La contrepartie, c’est la fragilité. Le hash couvre le script octet par octet, espaces comprises. Un fichier reformaté, un template qui interpole un identifiant utilisateur, un test A/B qui injecte une variante : chacun produit une empreinte différente et un script bloqué. Les hashes conviennent aux pages dont les scripts inline sont peu nombreux et stables, ce qui décrit la plupart des sites statiques et très peu d’applications personnalisées. Les produits multi-tenants sont pile sur cette frontière, et la CSP pour les plateformes SaaS détaille ce qui change quand chaque page est rendue pour un client connecté.
Deux extensions à connaître. 'unsafe-hashes' étend la correspondance aux gestionnaires d’événements inline (onclick="..."), une concession au markup ancien. Et en CSP Level 3, un hash peut autoriser un script externe dont le tag porte un attribut integrity correspondant, ce qui fusionne discrètement CSP et Subresource Integrity en un seul mécanisme. Chromium le gère, les autres moteurs ont traîné, testez avant d’en dépendre.
strict-dynamic change la question
Si vos scripts d’entrée chargent d’autres scripts (tous les tag managers, le chargement de chunks de la plupart des bundlers), tout lister est un tapis roulant sans fin. 'strict-dynamic' règle le problème : les scripts autorisés par nonce ou hash peuvent charger des scripts supplémentaires, qui héritent de la confiance. Les sources hôtes de la directive sont ignorées par les navigateurs modernes quand il est présent.
Le choix nonce-ou-hash ne doit donc couvrir que vos points d’entrée. Autorisez les trois tags script de votre HTML et laissez la confiance se propager, au lieu d’entretenir une allowlist de cinquante lignes qui se périme. Notre avis : si vous faites l’effort de retirer 'unsafe-inline', allez directement vers nonce plus 'strict-dynamic' ou hash plus 'strict-dynamic'. La demi-mesure de la grande allowlist d’hôtes coûte le même effort et vieillit plus mal.
Décider en un tableau
| Votre situation | Utilisez |
|---|---|
| Application server-rendered (HTML par requête) | Nonces + 'strict-dynamic' |
| Site statique, JAMstack, cache CDN pleine page | Hashes + 'strict-dynamic' |
| Mixte : coquille en cache, fragments dynamiques | Hashes pour la coquille, gardez les fragments sans scripts inline |
Application ancienne pleine d’attributs onclick= | Hashes + 'unsafe-hashes' le temps de migrer les gestionnaires |
| Scripts tiers hors de votre contrôle | Propagation 'strict-dynamic', ou sources hôtes en secours |
Savoir ce que vous auriez réellement à hasher
L’étape que tout le monde saute : avant de choisir, inventoriez les scripts inline que vous servez vraiment. Une politique report-only sans 'unsafe-inline' les fait lister par chaque navigateur, violation par violation, échantillon inclus. CentralCSP agrège ces rapports et son Builder transforme le trafic observé en politique relisible, le mécanisme report-sha256 capturant l’empreinte de chaque script vu par le navigateur. Cet inventaire tranche souvent le débat à lui seul : une douzaine de scripts inline stables plaide pour les hashes, un flux imprévisible plaide pour les nonces et pour une conversation avec l’équipe qui n’arrête pas d’en injecter.
Quel que soit le choix, la destination a la même forme : pas d’'unsafe-inline', des points d’entrée autorisés explicitement, la confiance propagée par 'strict-dynamic', les violations rapportées. La référence hashes et nonces couvre les détails de syntaxe le moment venu.
Questions fréquentes
Peut-on utiliser des nonces CSP sur un site statique ou derrière un CDN ?
Pas de façon sûre. Un nonce doit être une valeur aléatoire neuve à chaque réponse, or un hébergement statique ou un cache CDN pleine page sert les mêmes octets à tout le monde : le nonce se répète. Un nonce répété, c’est un mot de passe scotché sur la porte. Pages statiques et pages en cache doivent utiliser des hashes.
Pourquoi mon nonce ne fonctionne-t-il pas ?
Les trois causes habituelles : le nonce de l’en-tête ne correspond pas exactement à l’attribut nonce du tag script, la page est en cache donc l’en-tête et le HTML portent des nonces de réponses différentes, ou un proxy supprime ou réécrit l’un des deux. Comparez l’en-tête et la valeur dans le DOM sur une même réponse avant tout autre débogage.
Les hashes fonctionnent-ils pour les scripts externes ?
En CSP Level 3, oui : une source hash peut autoriser un script externe si son tag porte un attribut integrity correspondant. Chromium le gère. Les autres moteurs ont pris du retard, testez avant de vous y fier et gardez une source hôte ou un nonce en secours.
À quoi sert unsafe-hashes ?
Il étend la correspondance par hash aux gestionnaires d’événements inline comme onclick. C’est une aide à la migration pour du markup ancien que vous ne pouvez pas encore réécrire. Préférez déplacer les gestionnaires dans des scripts avec addEventListener. Chaque entrée unsafe-hashes est un petit trou qu’il faut savoir justifier.