# Comment tester une Content Security Policy en CI et en staging ?

> Trois couches pour attraper les régressions CSP avant les utilisateurs : la relecture du diff de politique, un scan noté qui casse le build sur une URL de préproduction, et le report-only sur du trafic réel que le pipeline ne produira jamais.

- Canonical: https://info.centralcsp.com/fr/articles/csp-in-ci-cd-pipeline/
- Published: 2026-09-21
- Language: fr
- Publisher: CentralCSP (https://centralcsp.com)

La plupart des déploiements CSP laissent un trou entre « ça marchait quand j'ai navigué sur le site » et « c'est appliqué en production », et c'est dans ce trou que vivent les régressions. Quelqu'un ajoute un widget de chat, la politique gagne un joker pour que ça passe, et personne ne le remarque avant qu'un audit, six mois plus tard, fasse observer que `script-src` autorise désormais la moitié d'internet.

Le montage qui tient vraiment comporte trois couches, et elles n'attrapent pas les mêmes choses. La relecture du diff de politique attrape l'affaiblissement délibéré. Un scan noté en CI, lancé contre une URL de preview ou de staging, casse le build sur les fautes de frappe et les directives dangereuses avant la fusion. Le report-only sur trafic réel attrape les pages et les navigateurs que votre pipeline ne visitera jamais. Sautez-en une et vous découvrirez laquelle.

Rien de tout cela n'exige une usine d'automatisation navigateur. Deux couches sur trois tiennent dans un appel curl et un en-tête.

## Couche un : la politique est du code

Si votre CSP est une chaîne dans un fichier de configuration, une conf de reverse proxy ou un middleware, elle est déjà relisible. La plupart des équipes ne la relisent jamais, parce que le diff ressemble à du bruit et que le relecteur n'a aucun moyen de juger si `https://*.vendor-cdn.net` est raisonnable.

Rendez-la relisible en découpant la politique en groupes nommés dans le code, un par raison d'être, avec un commentaire disant qui l'a demandé et quand :

```js
const scriptSrc = [
  "'self'",
  "'nonce-{NONCE}'",
  "'strict-dynamic'",
  // Prestataire de paiement, ajouté en 2026-03 par l'équipe checkout. Le retirer
  // casse le formulaire de carte. Revu avec eux en 2026-09, toujours nécessaire.
  'https://js.payments-vendor.example',
];
```

Le diff dit alors quelque chose. Une nouvelle entrée sans commentaire devient une discussion en relecture, et non un chantier d'archéologie l'année suivante. Cela ne coûte rien et attrape la façon la plus courante dont une politique pourrit, qui n'est pas un bug mais un relâchement délibéré, raisonnable sur le moment, jamais réexaminé.

Une règle vaut d'être tenue par convention : `'unsafe-inline'` et `'unsafe-eval'` n'entrent pas dans `script-src` sans un responsable nommé et une date de retrait. Sinon ils sont définitifs.

## Couche deux : noter le build en CI

C'est la couche sur laquelle on nous interroge, et celle qui manque à la plupart des pipelines.

Ce qui compte, c'est la cible du contrôle. Pas votre serveur de dev. Un serveur de dev injecte des scripts inline pour le rechargement à chaud, ce qui explique l'apparition de `'unsafe-inline'`, et il sert en général d'autres noms de bundles que la production. Visez un build de production : un déploiement de preview, une URL de staging, ou un conteneur démarré depuis l'artefact réel.

Ce qu'un scan attrape et qu'un navigateur ignore :

- **Les fautes de frappe.** Une directive `script-scr` n'est pas une erreur. C'est une directive inconnue, et les navigateurs ignorent les directives inconnues en silence. Le site fonctionne parfaitement et la politique a un trou de la taille exacte de cette directive.
- **Une politique assez permissive pour ne jamais rien casser.** `default-src *` passe tous les tests fonctionnels que vous pouvez écrire. C'est tout le problème des tests fonctionnels comme unique garde-fou.
- **Les endpoints JSONP et les contournements connus** sur des origines que vous avez autorisées, où un hôte permis sert un endpoint capable d'exécuter un callback fourni par l'attaquant.
- **L'en-tête disparu**, parce que quelqu'un a touché à la conf du proxy.

Pour un contrôle manuel, le [scanner CSP](https://centralcsp.com/fr/tools/csp-scanner/) gratuit prend une URL, récupère la politique en production y compris en Report-Only, et rend deux scores de 0 à 100 (sécurité et qualité) avec des findings classés par sévérité et un correctif pour chacun. Au-dessus de 80 c'est solide, de 50 à 79 il y a du travail, en dessous de 50 il y a de vrais trous. Sans compte, ce qui en fait le moyen le plus rapide de voir si cette couche trouverait quelque chose chez vous avant de l'industrialiser.

Pour la lancer à chaque build, il faut l'API REST, disponible à partir de l'offre Business (129,99 €/mois). Il n'y a pas d'action GitHub officielle à installer. C'est un appel curl et un code de sortie, ce que nous préférons, parce qu'une action est une dépendance de plus à maintenir :

```yaml
- name: Contrôle CSP
  env:
    CENTRALCSP_TOKEN: ${{ secrets.CENTRALCSP_TOKEN }}
  run: |
    # URL de base, chemins et noms de champs : voir la référence OpenAPI.
    SCORE=$(curl -sf -X POST "$CENTRALCSP_API/scans" \
      -H "Authorization: Bearer $CENTRALCSP_TOKEN" \
      -H 'Content-Type: application/json' \
      -d "{\"url\":\"$PREVIEW_URL\"}" | jq -r '.security_score')
    echo "Score sécurité CSP : $SCORE"
    [ "${SCORE%.*}" -ge 70 ] || { echo "Contrôle CSP échoué"; exit 1; }
```

Ce bout de script donne la forme, pas le copier-coller : prenez l'URL de base, le chemin du scan et le nom du champ de score dans la [référence OpenAPI](https://centralcsp.com/fr/docs/api-mcp/mcp/quickstart), parce qu'un schéma de réponse survit à tout article qui le cite. Les clés d'API de workspace agissent au nom de la personne qui les a créées, avec les mêmes rôles sur les sites et les mêmes limites d'offre, et la révocation est immédiate. Une clé de CI mérite donc d'être créée sous un utilisateur dédié en lecture plutôt que sous le compte d'un fondateur.

Deux remarques sur les seuils. Fixez un plancher, pas un objectif : un build qui doit scorer 90 sera désactivé dès le premier prestataire légitime ajouté. Et cassez franchement sur les findings catégoriques (en-tête absent, directive inconnue, `'unsafe-eval'` apparu là où il n'était pas) en vous contentant d'avertir sur les variations de score. Une barrière qui se déclenche sur du bruit se fait contourner, et une barrière contournée est pire que pas de barrière, puisque tout le monde la croit active.

Si votre pipeline dialogue déjà avec un assistant, les mêmes données passent par le serveur MCP intégré, sur la même offre : « le score a-t-il baissé depuis la semaine dernière et quel finding est nouveau » devient une question à poser plutôt qu'un script à maintenir.

## Couche trois : report-only, sur du trafic que vous n'avez pas produit

La CI parcourt les pages que vos tests visitent. Les utilisateurs réels visitent les autres.

Publiez la politique candidate en en-tête `Content-Security-Policy-Report-Only` avec un endpoint de reporting, et le navigateur rapporte ce qu'il aurait bloqué sans rien bloquer. Cela ne peut pas tomber le tunnel d'achat. C'est précisément sa raison d'être, et c'est la seule couche qui exerce le chemin Safari sur un vieil iPad, la locale qui charge un autre prestataire de consentement, et la page d'administration que personne n'a testée.

```http
Reporting-Endpoints: csp="https://MyEndpoint.report.centralcsp.com"
Content-Security-Policy-Report-Only: default-src 'none'; script-src 'self' 'nonce-{NONCE}' 'strict-dynamic'; report-uri https://MyEndpoint.report.centralcsp.com; report-to csp
```

En staging, cela donne un signal rapide venu de votre propre recette. En production, cela donne la vraie distribution, celle qui compte. Nous avons traité à part [la durée du report-only](/fr/articles/how-long-csp-report-only/) et [le passage du report-only à l'application](/fr/articles/report-only-to-enforced-migration/), et la réponse courte est qu'on s'arrête quand les violations inexpliquées tombent à zéro, pas quand le calendrier le dit.

Pour les environnements de preview, la difficulté est que le nom d'hôte change à chaque branche. CentralCSP restreint les origines autorisées à déposer des rapports pour un site via des filtres d'ingestion, ce qui empêche un inconnu de remplir votre tableau de bord. Les domaines de preview ont donc besoin que ce joker soit ajouté volontairement, et mieux vaut pointer les previews vers une entrée de site distincte plutôt que de mélanger le bruit des branches éphémères aux données du site de production et à son quota de rapports.

## À quoi sert chaque couche

| Couche | Attrape | Laisse passer |
| --- | --- | --- |
| Diff de politique en relecture | Affaiblissement délibéré, sources non documentées | Tout ce que personne ne relit sérieusement |
| Scan noté en CI | Fautes de frappe, directives dangereuses, en-tête absent, origines contournables | La casse sur les pages que le scan ne charge pas |
| Report-only sur trafic réel | La vraie casse, les pages oubliées, les écarts entre navigateurs | Rien, avec assez de temps, et c'est là son coût |

Le mode de panne de la couche deux seule, c'est une politique notée 95 qui casse le paiement sur Safari. Celui de la couche trois seule, c'est une politique qui ne casse rien parce qu'elle autorise tout. Elles se complètent, et aucune ne dispense de lire le diff.

## Débloquer d'abord le développement local

Avant que tout cela vaille la peine d'être automatisé, les développeurs doivent pouvoir itérer sur une politique sans déployer, parce qu'une boucle de retour qui se compte en minutes de CI produit un `'unsafe-inline'` avant midi.

L'extension Chrome gratuite fait ça en local, sans compte : son mode Rewrite substitue une politique candidate à celle du site, en application ou en report-only, pour votre session seulement, de sorte que vous pouvez vous connecter et dérouler un achat sous une politique non déployée. Le mode Build assemble un brouillon de politique à partir d'une base stricte pendant que vous naviguez. Nous avons décrit ce flux dans [tester une CSP sans la déployer](/fr/articles/test-csp-without-deploying/).

Faites d'abord tourner cette boucle, puis automatisez la barrière. Dans l'autre ordre, la barrière devient simplement la chose que les gens apprennent à contourner.

## Questions fréquentes

### Une pipeline CI peut-elle attraper tous les problèmes de CSP ?

Non, et croire le contraire est l'erreur classique. La CI parcourt les pages que votre suite de tests visite, dans un seul navigateur, sans utilisateurs réels. Elle attrape de façon fiable une politique affaiblie, une faute de frappe dans un nom de directive et un en-tête absent, ce qui couvre la majorité des régressions. Elle ne verra pas le widget propre à une locale sur une page que personne n'a testée. Le trafic réel en report-only s'en charge, et les deux couches ne se remplacent pas.

### Pourquoi ma CSP marche en local et casse en production ?

Presque toujours parce que le build de développement diffère du build de production. Les serveurs de dev injectent des scripts inline pour le rechargement à chaud, alors un développeur ajoute unsafe-inline pour avancer et cela ne ressort jamais. Les bundlers produisent aussi d'autres noms de fichiers et parfois d'autres origines CDN en production. Testez le build de production, et comparez la politique que votre pipeline émet réellement.

### Comment tester une CSP sur des environnements de preview éphémères ?

Publiez sur la preview une politique en report-only avec un endpoint de reporting, et laissez la branche collecter le temps de sa vie. Ce qu'on oublie, c'est le filtrage à l'ingestion : les domaines de preview changent à chaque branche, donc autorisez l'origine générique volontairement plutôt que de laisser n'importe quel hôte déposer des rapports dans les données de votre site de production.

### Faut-il casser le build sur un finding CSP, ou seulement avertir ?

Cassez sur ce qui est sans ambiguïté et peu coûteux à corriger : en-tête absent, nom de directive inconnu, unsafe-inline ou unsafe-eval apparaissant dans script-src alors qu'ils n'y étaient pas. Avertissez sur les mouvements de score, parce qu'un nouveau prestataire légitime peut faire bouger un score de quelques points, et une pipeline qui crie au loup est contournée en un mois.
