Ce que les en-têtes de sécurité HTTP vous apportent vraiment
2025-03-09
La sécurité la moins chère que vous ajouterez jamais
L'essentiel du travail de sécurité touche à la logique applicative — valider les entrées, restreindre la portée des jetons, réfléchir soigneusement à ce que chaque requête a le droit de faire. Les en-têtes de sécurité HTTP sont à l'opposé. Vous définissez une poignée d'en-têtes de réponse, le navigateur les applique à chaque chargement de page, et vous avez fermé des catégories entières d'attaques sans changer une ligne du comportement de votre app.
C'est là tout leur intérêt : un fort effet de levier par ligne de configuration, et le travail se situe en périphérie — une règle de CDN, un bloc de serveur, quelques lignes dans votre framework — sans avoir à toucher à toute votre codebase.
Une mise en garde avant la liste, car c'est ce que les gens comprennent de travers : les en-têtes relèvent de la défense en profondeur, ils ne remplacent pas le vrai correctif. Une Content-Security-Policy réduit le rayon d'explosion d'une faille de cross-site scripting ; elle ne corrige pas la faille. Considérez-les comme des couches externes autour d'un code déjà conçu pour être correct.

Je vais les passer en revue à peu près dans l'ordre où je les ajouterais, ce qui correspond aussi grossièrement au meilleur rapport bénéfice/effort en premier. Le dernier est l'exception — c'est celui que je retirerais désormais.
Strict-Transport-Security — imposer HTTPS et le maintenir imposé
Strict-Transport-Security: max-age=31536000; includeSubDomains
Ce qu'il fait. Connu sous le nom de HSTS, il indique au navigateur de ne jamais communiquer avec votre site qu'en HTTPS pendant la durée de max-age (ici, un an en secondes). Une fois qu'un navigateur a vu cet en-tête, il fait lui-même passer toute requête http:// en https://, avant qu'un seul octet ne parte sur le réseau. includeSubDomains étend cette promesse à chaque sous-domaine.
Ce qu'il empêche. L'attaque par rétrogradation. Sans HSTS, la toute première requête d'un utilisateur — un domaine nu tapé dans la barre d'adresse, ou un vieux lien http:// — part en clair, et cette requête en clair est précisément là où un homme du milieu sur un Wi-Fi hostile retire le TLS et se glisse entre l'utilisateur et le serveur. HSTS supprime cette fenêtre : après la première visite, le navigateur n'essaie même plus d'utiliser HTTP.
Ce à quoi il vaut la peine de réfléchir. Il existe une directive preload et une liste de préchargement associée dans les navigateurs, qui éliminent même le risque de la première visite — votre domaine est livré codé en dur dans les navigateurs comme étant exclusivement HTTPS. C'est réellement plus solide, mais voyez-y un engagement, pas une simple option. Entrer dans la liste est facile ; en sortir est lent, et cela s'applique à chaque hôte sous votre domaine. N'ajoutez preload que lorsque vous êtes certain que chaque sous-domaine que vous exploiterez un jour pourra faire du HTTPS, indéfiniment. En attendant, un max-age d'un an avec includeSubDomains est la valeur par défaut sûre.
Content-Security-Policy — le gros morceau, et le plus de travail
Content-Security-Policy: default-src 'self'
Ce qu'il fait. La CSP indique au navigateur depuis quelles sources il a le droit de charger des ressources — scripts, styles, images, frames, polices, tout. default-src 'self' est le point de départ brutal : ne charger que des choses depuis ma propre origine, rien d'externe.
Ce qu'il empêche. C'est votre réponse la plus forte au XSS. Si un attaquant parvient à injecter <script src="https://evil.example/x.js"> dans votre page, une CSP qui ne liste pas cette origine fait que le navigateur refuse purement et simplement de la récupérer et de l'exécuter. Cela coupe aussi l'exfiltration vers des domaines non approuvés.
La mise en garde honnête. default-src 'self' est là où vous commencez et presque jamais là où vous finissez. Dès que vous chargez de l'analytique, une police web, une vidéo intégrée, ou quoi que ce soit depuis un CDN, vous écrivez des listes d'autorisation explicites — script-src, style-src, img-src, frame-src, et ainsi de suite. Les balises <script> en ligne et les attributs style cessent de fonctionner à moins que vous ne les autorisiez, et « il suffit d'ajouter 'unsafe-inline' » vous prive discrètement de la majeure partie de la protection que vous étiez venu chercher. Une CSP moderne et solide utilise plutôt des nonces ou des hachages par requête, plus strict-dynamic. De tous les en-têtes ici, c'est celui qui est le plus susceptible de casser votre propre site avant même d'arrêter un attaquant — alors déployez-le d'abord avec Content-Security-Policy-Report-Only, observez ce qu'il aurait bloqué, et ne l'imposez qu'ensuite.
Encore une chose : la directive frame-ancestors de la CSP est le remplacement moderne et plus flexible de l'en-tête suivant de cette liste. Si vous écrivez une CSP, définissez-y aussi frame-ancestors.
X-Content-Type-Options — une ligne, aucun inconvénient
X-Content-Type-Options: nosniff
Ce qu'il fait. Indique au navigateur de faire confiance au Content-Type que vous avez déclaré et de cesser d'essayer de sniffer le vrai type d'une ressource à partir de ses octets.
Ce qu'il empêche. Le MIME sniffing. Si votre site accepte des fichiers envoyés par les utilisateurs et les sert ensuite, un attaquant peut fournir un fichier déclaré comme image/jpeg, mais qui contient en réalité du JavaScript — et un navigateur qui sniffe le contenu et décide que cela ressemble à un script l'exécutera. nosniff force le navigateur à honorer le type que vous avez déclaré, de sorte qu'un fichier servi comme image est traité comme une image, point final.
Pourquoi c'est le premier à définir. Il n'y a pour ainsi dire aucun inconvénient et rien à régler. Une seule valeur statique qui ferme un vrai trou. Mettez-le partout et oubliez-le.
X-Frame-Options — n'autorisez pas d'autres sites à porter votre interface
X-Frame-Options: DENY
Ce qu'il fait. Empêche vos pages d'être intégrées dans un <iframe> sur un autre site. DENY bloque tout affichage dans une frame ; SAMEORIGIN l'autorise seulement entre pages de la même origine.
Ce qu'il empêche. Le clickjacking. L'attaque charge votre vraie page dans un iframe transparent superposé à une fausse interface, de sorte que l'utilisateur croit cliquer sur le bouton anodin de l'attaquant alors qu'il clique en réalité sur votre commande « supprimer le compte » ou « autoriser le paiement » en dessous. Si votre page ne peut pas être mise en cadre, la superposition ne peut pas être construite.
La note moderne. Comme ci-dessus, frame-ancestors de la CSP est le mécanisme le plus récent et l'emporte là où les deux sont présents. X-Frame-Options vaut encore la peine d'être envoyé pour les clients plus anciens, mais si vous écrivez déjà une CSP, frame-ancestors 'none' (ou une liste d'autorisation précise) est le vrai contrôle.
Referrer-Policy — cessez de divulguer vos URL
Referrer-Policy: strict-origin-when-cross-origin
Ce qu'il fait. Contrôle quelle part de l'URL d'origine accompagne la navigation dans l'en-tête Referer quand un utilisateur clique vers un autre site. Cette valeur envoie le chemin complet pour une navigation de même origine, mais seulement l'origine nue (https://example.com, sans chemin ni requête) lors du passage vers un site différent — et rien du tout lors d'une rétrogradation de HTTPS vers HTTP.
Ce qu'il empêche. Les fuites discrètes de vie privée et de données. Une URL comme https://example.com/reset?token=secret ne devrait jamais confier ce jeton au lien externe sur lequel l'utilisateur clique ensuite. Cette politique garantit que seule votre origine quitte les lieux.
La nuance. C'est déjà la valeur par défaut dans les navigateurs modernes ; la définir explicitement ne change donc pas vraiment le comportement, mais rend l'intention claire et stable, y compris pour les clients plus anciens. Peu coûteux à définir, et cela documente une décision que vous avez réellement prise plutôt qu'une décision héritée.
Permissions-Policy — désactivez le matériel que vous n'utilisez pas
Permissions-Policy: camera=(), microphone=(), geolocation=()
Ce qu'il fait. Indique quelles fonctionnalités sensibles du navigateur sont accessibles à votre site et aux contenus qu'il intègre. Une liste d'autorisation vide, camera=(), refuse purement et simplement cette fonctionnalité.
Ce qu'il empêche. L'abus de fonctionnalités, surtout via des intégrations tierces. Si votre site n'utilise jamais la caméra, le microphone ou la localisation, le déclarer signifie qu'une publicité malveillante ou un script compromis ne peut pas discrètement demander l'accès à ces API ni les atteindre. Vous réduisez la surface d'attaque à exactement ce dont vous avez besoin.
Bon à savoir. Cet en-tête a remplacé l'ancien Feature-Policy, qui utilisait une syntaxe différente — si vous trouvez des exemples de Feature-Policy en ligne, ils sont périmés. Désactivez les fonctionnalités inutiles et limitez celles dont vous avez besoin à votre propre origine.
X-XSS-Protection — celui qu'il faut abandonner
X-XSS-Protection: 0
C'est l'en-tête qui a le plus mal vieilli, et le conseil qu'on trouve encore à son sujet — « mettez 1; mode=block comme solution de repli » — est ce que je contesterais le plus fermement.
Il était censé activer le filtre XSS intégré au navigateur : un mécanisme heuristique qui essayait de détecter les scripts injectés dans une requête puis renvoyés dans la réponse, pour les neutraliser. Le problème, c'est que le filtre a disparu. Chrome a supprimé son XSS Auditor dès 2019, Edge a suivi lors de son passage à Chromium, et Firefox n'en a jamais livré. Dans un navigateur actuel, 1; mode=block ne fait rien.
Pire, tant qu'il était actif, l'auditeur est devenu une surface d'attaque à part entière — sa détection pouvait être transformée en canal auxiliaire pour sonder une page, et son blocage pouvait être déclenché pour supprimer du contenu légitime. C'est pourquoi la recommandation actuelle, y compris celle de l'OWASP Secure Headers Project, est d'omettre entièrement l'en-tête ou d'envoyer X-XSS-Protection: 0 pour désactiver explicitement tout comportement hérité résiduel. La vraie protection contre le XSS, c'est la Content-Security-Policy ci-dessus, plus un encodage de sortie discipliné dans votre app. Cet en-tête est une relique ; traitez-le comme telle.
Mettre le tout en place
Tous ces en-têtes vivent au niveau de la réponse ; ils sont donc indépendants du framework et du langage — un add_header Nginx, une règle Vercel ou Cloudflare, ou la configuration d'en-têtes propre à votre framework vous mènent tous au même résultat. Voici l'ensemble complet câblé dans une configuration Next.js, puisque c'est ce que fait tourner ce site :
// next.config.ts
const securityHeaders = [
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
{ key: "Content-Security-Policy", value: "default-src 'self'" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{ key: "X-XSS-Protection", value: "0" },
];
export default {
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
Réglez la CSP selon vos vraies dépendances avant de la mettre en production — cette unique ligne bloquera votre propre analytique et vos polices, exactement telle qu'elle est écrite.
Ensuite, vérifiez votre travail, car un en-tête que vous croyez avoir défini et un en-tête que le navigateur reçoit réellement sont deux choses différentes. curl -I https://yoursite.example vous montre exactement ce qui passe sur le réseau, et un scanner comme securityheaders.com note l'ensemble et signale ce qui manque. Je préfère voir la réponse que de me fier à la configuration.
Rien de tout cela ne remplace le fait de bien concevoir l'application elle-même. Mais pour l'effort consenti — une douzaine de lignes, définies une fois, imposées par le navigateur de chaque visiteur — c'est le meilleur compromis sécurité-par-frappe que vous trouverez. Définissez les six qui méritent leur place, abandonnez celui qui ne le mérite pas, et vérifiez ce qui est réellement livré.
Si ce genre de sujet vous intéresse, j'écris la suite dans la newsletter.