Un site statique part avec une longueur d’avance en sécurité. Pas de base de données où injecter, pas de session à voler, pas de code serveur à exploiter. Le build devient des fichiers, et un CDN les sert.

C’est là que la plupart des gens s’arrêtent, et il reste un trou. Le navigateur décide toujours de la façon de traiter ces fichiers, et il en décide à partir des en-têtes de la réponse. Par défaut, la plupart des hébergeurs n’en envoient presque aucun, et le navigateur se rabat sur des réglages permissifs : n’importe quel site peut encadrer vos pages, un fichier peut être lu comme un type qu’il n’a jamais déclaré, et une première visite peut commencer en HTTP simple.

Où vivent les en-têtes

Sur un site statique, les en-têtes relèvent de la configuration de l’hébergeur, pas du code. Netlify et Cloudflare Pages lisent un fichier _headers dans le dossier de build (placez-le dans public/ et Astro le recopie) ; Vercel prend un bloc headers dans vercel.json. Mêmes valeurs, syntaxe différente.

La liste courte

/*
  Strict-Transport-Security: max-age=63072000; includeSubDomains
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()
  X-Frame-Options: DENY
  Content-Security-Policy-Report-Only: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

Ce que chaque ligne apporte :

  • Strict-Transport-Security impose HTTPS au navigateur pour ce domaine, pendant deux ans. Il ferme la fenêtre où une première visite en HTTP simple pourrait être interceptée et rétrogradée. N’ajoutez preload qu’une fois que tous les sous-domaines servent du HTTPS : on entre facilement dans la liste de préchargement, on en sort lentement.
  • X-Content-Type-Options: nosniff empêche le navigateur de remettre en cause le type de contenu déclaré. Sans lui, un fichier servi sous un type peut être interprété comme un autre, et c’est ainsi qu’un envoi d’apparence anodine finit exécuté comme un script.
  • Referrer-Policy n’envoie aux autres sites que votre origine, jamais le chemin complet ni la chaîne de requête, là où fuient souvent les jetons et les termes de recherche.
  • Permissions-Policy désactive les API sensibles du navigateur dont le site ne se sert pas. Si un script tiers est un jour compromis, il ne pourra toujours pas demander la caméra.
  • X-Frame-Options: DENY et la directive CSP frame-ancestors 'none' empêchent d’autres sites d’encadrer le vôtre, le montage derrière le clickjacking. La directive CSP est la version moderne ; l’ancien en-tête couvre les anciens navigateurs.

Content-Security-Policy, d’abord en report-only

CSP est l’en-tête le plus puissant de la liste, et le plus facile à mal régler. C’est une liste d’autorisations qui dit d’où peuvent venir scripts, styles, images et connexions : une politique trop stricte casse la page d’une façon qui ne se voit qu’en production.

C’est pour cela que la liste l’envoie en Content-Security-Policy-Report-Only. Le navigateur signale chaque violation dans la console et ne bloque rien. Parcourez chaque page, corrigez ou autorisez ce qui remonte, et seulement ensuite renommez l’en-tête en Content-Security-Policy pour l’appliquer.

Deux ajustements reviennent sur presque tous les sites. style-src a besoin de 'unsafe-inline' tant qu’un composant pose un attribut style, et form-action doit nommer la destination du formulaire de contact si ce n’est pas votre propre origine. Des statistiques ou des polices hébergées ailleurs ont aussi droit à leurs propres entrées.

Vérifier

Après un déploiement, lisez les en-têtes directement sur la réponse :

curl -sI https://example.com/ | grep -iE "strict-transport|content-security|x-frame|referrer|permissions|nosniff"

Pour un second avis, l’HTTP Observatory de Mozilla note ces mêmes en-têtes et explique tout ce qu’il signale. Ça prend une minute, et c’est la revue de sécurité la moins chère qu’un site obtiendra jamais.