Validateur de CSP

La plupart des politiques autorisent plus que ne le croit leur auteur.

Collez une Content-Security-Policy et voyez ce qu'un navigateur laissera réellement passer. Rien ne nous est envoyé : la politique est analysée dans votre navigateur, ce qui vous permet de vérifier un en-tête que vous n'avez pas encore déployé.

Tourne dans votre navigateur. Rien ne nous est envoyé.

Ce que cela vérifie, et à quoi ressemble une bonne réponse

Quelle directive régit réellement vos scripts

Les navigateurs utilisent script-src si elle est présente et se rabattent sur default-src sinon. Ce repli est une source fréquente de surprise : une politique avec un default-src strict et un script-src permissif est exactement aussi permissive que le second, et la ligne stricte au-dessus ne fait rien pour les scripts. Nous vous montrons quelle directive nous avons lue, pour que le verdict soit vérifiable plutôt qu'à prendre sur parole.

unsafe-eval

Permet de transformer des chaînes en code exécutable, ce dont dépend la plupart des charges utiles d'injection. Il est généralement là parce qu'un réglage de bundler ou une bibliothèque de templates l'a réclamé, et il est généralement supprimable en changeant ce réglage. Tant qu'il est là, une grande partie du reste de la politique est décorative.

Jokers, et quand ils ne comptent pas

Un * isolé, ou une source de schéma comme https:, autorise du script depuis n'importe quel hôte d'internet. Mais un joker d'hôte posé à côté de 'strict-dynamic' n'est pas un trou, car ce mot-clé demande au navigateur d'ignorer entièrement les sources d'hôte et de ne faire confiance qu'à ce que charge un script approuvé par nonce. Nous vérifions des tokens entiers plutôt que de chercher dans le texte : https://cdn.exemple.fr/* est donc correctement lu comme un hôte précis et non comme un joker sous prétexte qu'il contient une étoile.

unsafe-inline, et quand il est déjà ignoré

La raison la plus fréquente pour laquelle une politique d'apparence stricte ne tient pas. Elle autorise les scripts inline, donc le markup injecté s'exécute. La nuance qui fait trébucher la plupart des vérificateurs : si la même directive porte un nonce ou un hash, les navigateurs ignorent complètement 'unsafe-inline'. Les politiques sont écrites ainsi volontairement pour que les navigateurs anciens, qui ne comprennent pas les nonces, chargent quand même la page. Signaler cette forme serait un faux positif sur la politique stricte correcte la plus répandue qui soit : nous la déclarons donc conforme et nous disons pourquoi.

base-uri, celle qui manque presque toujours

base-uri n'est pas couverte par default-src : une politique peut donc sembler complète tout en l'omettant. Sans elle, une balise <base> injectée réoriente chaque script et chaque lien relatif de la page vers un autre hôte, ce qui transforme une petite injection HTML en exécution complète de scripts et contourne proprement le script-src sur lequel vous avez beaucoup travaillé. Cela coûte une directive.

object-src et frame-ancestors

object-src 'none' ferme le contenu de plugin, dont quasiment aucune application n'a besoin et qui peut sinon exécuter un document dans votre origine. frame-ancestors décide qui peut intégrer votre page ; c'est le remplaçant moderne de X-Frame-Options et la seule des deux capable d'exprimer une liste d'origines autorisées.

Comment corriger

Une politique stricte est plus facile à atteindre que sa réputation ne le laisse croire, à condition de la déployer d'abord en mode report-only et de lire ce qui casse. L'approche par nonce ci-dessous est celle qu'utilise ce site même : c'est donc un schéma que nous faisons tourner en production plutôt qu'un simple conseil.

Une politique stricte pour commencer
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

# 'strict-dynamic' lets a nonce-approved script load what it needs
# without you listing every CDN, which is what makes a strict policy
# survivable in a real app with a bundler.
Déployez-la sans risque d'abord
# Same policy, different header name. Browsers report violations
# to the console and to report-to, and block NOTHING. Run it for a
# week, read what shows up, then rename the header.
Content-Security-Policy-Report-Only:
  default-src 'self'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'
Générer le nonce (Cloudflare Worker)
// The nonce must be new on EVERY request. A nonce baked into a
// static file is not a nonce, it is a password the attacker also has.
const nonce = crypto.randomUUID().replaceAll("-", "");

const policy = [
  "default-src 'self'",
  `script-src 'nonce-${nonce}' 'strict-dynamic'`,
  "object-src 'none'",
  "base-uri 'self'",
  "frame-ancestors 'none'",
].join("; ");

// Then stamp the same nonce onto every <script> as you stream the HTML.
return new HTMLRewriter()
  .on("script", { element: (el) => el.setAttribute("nonce", nonce) })
  .transform(new Response(html, { headers: { "content-security-policy": policy } }));
Middleware Next.js
// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request: Request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const policy = `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self'`;

  const headers = new Headers(request.headers);
  headers.set("x-nonce", nonce);

  const response = NextResponse.next({ request: { headers } });
  response.headers.set("content-security-policy", policy);
  return response;
}
Ce que cet outil ne peut pas voir

Ceci lit la politique que vous avez collée. Cela ne peut pas vous dire s'il s'agit bien de la politique que votre serveur envoie, et les deux divergent plus souvent qu'on ne le croit : un proxy, une règle de CDN ou un second en-tête au niveau du framework peuvent l'écraser ou la dupliquer, et quand deux politiques arrivent le navigateur applique l'intersection des deux. Cela ne peut pas non plus vous dire si la politique casse votre application, ce que seul un déploiement en report-only montrera. Pour voir ce que votre site en ligne envoie, lancez le vérificateur d'en-têtes de sécurité sur l'URL.

Scanner aussi le codeGratuit pour démarrer. Connectez un dépôt et le premier scan tourne en deux minutes environ.