Retour au blog

Votre script d'analytics est le trou dans votre Content-Security-Policy

Une CSP stricte bloque le XSS. Un tag d'analytics tiers le rouvre. Voici pourquoi les allowlists d'hôtes et l'absence de SRI sabotent votre politique.

Une Content-Security-Policy est la défense la plus efficace contre le cross-site scripting. Dès que vous ajoutez un tag d'analytics tiers, vous devez généralement y percer un trou. C'est précisément dans ce trou que vivent la plupart des contournements de CSP rencontrés en production.

La raison est structurelle, pas accidentelle. Les scripts d'analytics gourmands en surveillance sont conçus pour charger davantage de code à l'exécution, depuis des domaines que vous ne contrôlez pas. Une CSP stricte existe justement pour interdire exactement cela.

Les allowlists d'hôtes sont une façon faible de faire confiance à un script

L'ancienne méthode pour autoriser un fournisseur d'analytics était une allowlist d'hôtes :

Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;

L'OWASP ne recommande plus ce schéma. Les allowlists sont facilement contournables : toute origine autorisée qui héberge un endpoint JSONP, une redirection ouverte ou des scripts uploadés par les utilisateurs devient un vecteur de XSS. Les tag managers aggravent la situation, car ils existent précisément pour injecter d'autres scripts depuis des origines arbitraires.

La bonne pratique actuelle est une CSP stricte bâtie sur un nonce par réponse, couplé à strict-dynamic :

Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';

Avec strict-dynamic, le navigateur fait confiance aux scripts chargés par un script déjà approuvé — ce qui correspond exactement au comportement dont dépend un tag manager. C'est pratique pour le fournisseur et dangereux pour vous : un seul tag compromis a désormais la permission implicite de charger n'importe quoi.

SRI ne fonctionne pas sur les scripts qui en ont le plus besoin

La Subresource Integrity permet au navigateur de vérifier un fichier récupéré contre un hash cryptographique avant de l'exécuter :

<script
  src="https://cdn.example.com/tracker.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"></script>

SRI prend en charge sha256, sha384 et sha512, et le navigateur refuse la ressource en cas du moindre écart. C'est une défense nette contre un CDN compromis — pour des fichiers statiques et figés par version.

Elle s'effondre face aux analytics. Les tags des fournisseurs sont délibérément mutables : le fichier à la même URL change dès que le fournisseur livre des fonctionnalités, donc un hash figé casserait la collecte au prochain déploiement. En pratique, les équipes abandonnent SRI sur exactement les scripts qui ont la portée la plus large dans la page. La recalibration d'octobre 2025 du risque lié à la supply chain a classé une implémentation non sécurisée de SRI en sévérité élevée pour cette raison.

Trusted Types relèvent le niveau en février 2026

La moitié DOM-XSS du problème a désormais une réponse au niveau du navigateur. Depuis le Baseline février 2026, les Trusted Types sont disponibles dans les navigateurs actuels. Vous activez l'application avec :

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;

Ensuite, les sinks DOM comme Element.innerHTML rejettent les chaînes brutes et n'acceptent que des valeurs produites par une politique enregistrée :

const policy = trustedTypes.createPolicy("default", {
  createHTML: (input) => DOMPurify.sanitize(input),
});
el.innerHTML = policy.createHTML(userInput); // ok
el.innerHTML = userInput;                     // throws TypeError

C'est réellement solide. C'est aussi le genre de règle que le code d'analytics et de tag manager hérité viole régulièrement, parce que ces scripts écrivent dans innerHTML et injectent des nœuds <script> sous forme de chaînes brutes. Activer les Trusted Types signifie souvent désactiver d'abord votre fournisseur d'analytics.

Un tracker first-party s'inscrit dans une politique stricte au lieu de la combattre

Le conflit disparaît quand le script d'analytics est petit, autonome et servi depuis votre propre origine. Un tracker qui ne charge aucun code supplémentaire n'a besoin ni de strict-dynamic ni d'un hôte fournisseur dans votre allowlist :

Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';

C'est tout. Aucune origine tierce, aucune escalade par nonce, aucune exception taillée pour un tag manager.

Le tracker de Monoid pèse environ 2 Ko, n'a aucune dépendance et n'appelle jamais de sink DOM-XSS. Il n'écrit pas dans innerHTML, n'injecte pas de scripts et ne lit ni n'écrit de cookies ou de stockage. Il envoie une requête keepalive par page vue vers /collect et reste inactif le reste du temps :

fetch('/collect', {
  method: 'POST',
  body: JSON.stringify({ site_id, path, referrer, screen_w }),
  keepalive: true,
});

Comme le fichier est statique, vous pouvez lui appliquer SRI sans jamais rien casser — un hash figé reste valide jusqu'à ce que vous choisissiez de mettre à jour le script. Et comme l'identité est un hash quotidien à sens unique, SHA-256(IP | UA | SALT_SECRET | YYYY-MM-DD), il n'y a aucun profilage inter-sites qu'une CSP relâchée pourrait même laisser fuiter.

Le schéma se généralise au-delà des analytics : chaque script que vous retirez de votre allowlist est une classe de contournement XSS que vous retirez avec lui. Le script tiers le plus sûr est celui que vous ne chargez pas.

Sources

Comments

Loading comments…