# Où vont les rapports CSP du navigateur et comment les recevoir (/fr/blog/where-to-send-csp-reports)





Un rapport de politique de sécurité du contenu (CSP) va là où vous dites au navigateur de l'envoyer : une URL que vous mettez dans la politique. Quand le navigateur bloque quelque chose que la politique interdit, il construit un petit document JSON décrivant la violation et le poste à cette URL. Il n'y a aucune magie côté réception. Tout serveur qui accepte un POST HTTPS et lit un corps JSON peut être un endpoint de rapport. Les questions intéressantes sont ce que le navigateur envoie réellement, quelle part arrive, et si un simple endpoint suffit ou si vous voulez un collecteur qui groupe et alerte.

Cet article couvre ce qu'est un endpoint de rapport, l'exigence HTTPS, pourquoi le cross-origin convient, les deux content types de POST que le navigateur utilise, le volume auquel vous attendre, et un regard honnête sur construire votre propre endpoint plutôt que d'en utiliser un hébergé.

## Un endpoint de rapport est juste une URL vers laquelle le navigateur poste [#un-endpoint-de-rapport-est-juste-une-url-vers-laquelle-le-navigateur-poste]

Vous nommez la destination dans la politique. Avec la mise en place moderne, vous déclarez un endpoint nommé dans le header de réponse [`Reporting-Endpoints`](/fr/docs/web-security/reporting-api/headers/reporting-endpoints), puis vous pointez la politique vers ce nom avec la directive `report-to` :

```http
Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"
```

```http
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint
```

Quand la politique bloque une ressource, le navigateur collecte la violation et envoie un POST HTTP à cette URL avec un corps JSON. Votre endpoint n'a rien à faire de spécial pour être valide. Il doit accepter le POST, renvoyer un statut 2xx, et (si vous voulez garder les données) lire et stocker le corps. Le navigateur ne lit pas votre corps de réponse et ne réessaie pas sur la plupart des erreurs, donc un endpoint absent ou lent perd silencieusement des rapports au lieu de casser la page.

Pour l'ancien mécanisme, la directive [`report-uri`](/fr/docs/web-security/policies/content-security-policy/directives/report-uri) contient l'URL directement au lieu d'une référence nommée. Dans les deux cas, la destination est une URL et le transport est un POST HTTP. La différence entre les deux mécanismes est traitée dans [report-uri vs report-to](/fr/blog/report-uri-vs-report-to).

## HTTPS est obligatoire [#https-est-obligatoire]

L'URL de l'endpoint doit être en `https://`. Une page sécurisée n'enverra pas de rapports à un endpoint `http://` non sécurisé, et les navigateurs écartent les destinations de reporting qui dégraderaient la connexion. En pratique, ce n'est jamais une vraie contrainte, votre collecteur devrait de toute façon être en HTTPS, mais c'est bon à savoir si un endpoint de test en HTTP simple semble ne rien recevoir. La solution est de le mettre derrière TLS, pas de déboguer la politique.

## Le cross-origin est autorisé et prévu [#le-cross-origin-est-autorisé-et-prévu]

Votre endpoint de rapport n'a pas à vivre sur la même origine que la page. Envoyer les rapports vers un autre hôte, un collecteur dédié ou un service tiers est le cas normal, pas un contournement. Le Reporting API a été conçu pour cela : un POST de rapport CSP est envoyé vers l'URL que le header nomme, sur n'importe quelle origine, sans qu'un preflight CORS ne le bloque.

C'est pourquoi un collecteur hébergé fonctionne tout court. La page sur `https://shop.example` peut déclarer un `report-to csp-endpoint` qui pointe vers `https://<Endpoint-ID>.report.centralcsp.com`, et le navigateur y livrera les violations. Vous n'avez pas besoin de faire d'abord transiter les rapports par votre propre backend. Faites tourner le collecteur où vous voulez.

## Les deux content types que le navigateur poste [#les-deux-content-types-que-le-navigateur-poste]

Le corps que le navigateur envoie est du JSON, mais il y a deux formes différentes et deux valeurs de `Content-Type` différentes, selon le mécanisme qui a livré le rapport. Votre endpoint doit gérer les deux, car une politique porte souvent les deux directives pendant une migration.

Le Reporting API moderne utilise `application/reports+json`. Le corps est un **tableau** d'enveloppes de rapport, chacune avec `type`, `age`, `url`, et un objet `body` contenant les champs de violation. Plusieurs rapports peuvent être regroupés dans un seul POST :

```json
[
  {
    "age": 12,
    "type": "csp-violation",
    "url": "https://shop.example/checkout",
    "body": {
      "documentURL": "https://shop.example/checkout",
      "effectiveDirective": "script-src",
      "blockedURL": "https://evil.example/x.js",
      "disposition": "report",
      "statusCode": 200
    }
  }
]
```

Le mécanisme hérité `report-uri` utilise `application/csp-report`. Le corps est un **objet unique** enveloppé dans une clé `"csp-report"`, avec les anciens noms de champs :

```json
{
  "csp-report": {
    "document-uri": "https://shop.example/checkout",
    "violated-directive": "script-src",
    "blocked-uri": "https://evil.example/x.js",
    "disposition": "report"
  }
}
```

Les deux formats portent la même idée avec des noms de champs différents et une enveloppe différente. Pour la correspondance champ par champ et la structure complète, voir [la référence du format de livraison des rapports](/fr/docs/web-security/reporting-api/concepts/report-delivery-format). Un endpoint qui ne parse qu'un seul content type jettera silencieusement la moitié de ses données dès qu'une politique porte les deux directives, donc branchez selon le header `Content-Type` et gérez chacun.

## Attendez-vous à du volume, et à du bruit [#attendez-vous-à-du-volume-et-à-du-bruit]

Un vrai site génère beaucoup de rapports, et la plupart ne sont pas exploitables isolément. Trois choses font grimper le compte :

* **Regroupement et timing.** Les rapports modernes peuvent arriver regroupés dans un POST ou au compte-gouttes séparément, parfois des secondes ou des minutes après la violation, car le navigateur les met en file et les livre selon son propre calendrier.
* **Quasi-doublons.** Un script inline cassé sur une page populaire produit la même violation chez chaque visiteur qui la charge, vous obtenez donc des milliers d'enregistrements qui disent la même chose.
* **Bruit des extensions et des scripts injectés.** Les extensions, les injecteurs d'antivirus et les scripts injectés par les FAI déclenchent la politique côté client et génèrent des rapports qui n'ont rien à voir avec votre code. Les extensions de navigateur en particulier (bloqueurs de pub, gestionnaires de mots de passe et similaires) sont largement documentées comme la plus grosse source de bruit dans les rapports CSP, prévoyez donc de les filtrer.

La conséquence, c'est que les rapports bruts sont difficiles à exploiter. Vous ne voulez pas d'une liste de cinquante mille lignes. Vous voulez connaître les origines distinctes et les blocs inline dont vos pages ont réellement besoin, lesquels sont du bruit, et si quelque chose de nouveau vient d'apparaître. Cela veut dire qu'il vous faut grouper par directive et ressource bloquée, dédupliquer et filtrer, avant que les données soient utiles. Un endpoint jetable qui ne fait qu'ajouter chaque POST à un log vous laisse tout ce travail.

## Construire ou acheter votre collecteur [#construire-ou-acheter-votre-collecteur]

Vous avez deux options honnêtes, et la bonne dépend de jusqu'où vous voulez aller.

**Construire un endpoint jetable** quand vous avez juste besoin de voir si le reporting est câblé, ou que vous déboguez une politique sur un site de préproduction. Quelques lignes qui acceptent le POST, branchent selon le content type et écrivent le JSON quelque part suffisent à confirmer que les rapports affluent. C'est peu coûteux et convient pour un essai rapide. Ce qu'il ne vous donne pas, c'est le regroupement, la déduplication, la rétention, ni aucun moyen de distinguer d'un coup d'œil une vraie injection d'une extension bruyante. Vous lirez du JSON brut, et au volume de production cela cesse vite de fonctionner.

**Exploiter ou utiliser un collecteur** quand le reporting est quelque chose dont vous dépendez. Un collecteur, c'est l'endpoint plus tout ce que vous vouliez vraiment : il accepte les deux content types, groupe les violations quasi-doublons, déduplique, sépare le bruit des extensions des vrais problèmes, garde l'historique pour que vous voyiez quand un nouveau script est apparu, et vous alerte quand quelque chose change sur une page sensible. Vous pouvez le construire vous-même, c'est un vrai projet avec un stockage de données, une ingestion et une interface, ou vous pouvez pointer la politique vers un collecteur hébergé.

Cette option hébergée, c'est la [suite CSP de CentralCSP](/platform/csp-builder). Vous déclarez son URL comme votre endpoint, le navigateur y poste les violations, et au lieu de lignes brutes vous les obtenez groupées par directive et origine, avec le bruit séparé et les scripts de chaque page inventoriés via le reporting de hash CSP. La décision est la même que d'habitude : un logger que vous maintenez, ou un collecteur qui fait le regroupement et les alertes pour vous.

Si vous avez seulement besoin de vérifier ce qu'un site en production envoie actuellement et si sa politique pointe quelque part, le [scanner CSP](/tools/csp-scanner) gratuit lit les headers déployés sans aucune configuration d'endpoint.

<img alt="Les violations issues du trafic réel, regroupées par directive et origine bloquée" src="__img0" width="1365" height="691" />

## Un endpoint minimal, si vous le construisez vous-même [#un-endpoint-minimal-si-vous-le-construisez-vous-même]

Pour un endpoint local rapide qui capture juste les deux formes, branchez selon le content type et stockez le corps. C'est la version jetable, pas un collecteur :

```javascript title="server.js"
app.post("/csp-reports", (req, res) => {
  const type = req.headers["content-type"] || "";
  if (type.includes("application/reports+json")) {
    // modern: req.body is an array of report envelopes
    for (const report of req.body) store(report.body);
  } else if (type.includes("application/csp-report")) {
    // legacy: req.body is a single { "csp-report": {...} } object
    store(req.body["csp-report"]);
  }
  res.sendStatus(204);
});
```

Utilisez un parseur de corps JSON brut pour les deux content types, renvoyez `204`, et ne laissez jamais le handler lever une exception, un 5xx ne fait que perdre le rapport. Stocker les lignes est la partie facile. Le regroupement, la déduplication et les alertes par-dessus sont le travail, et la raison pour laquelle la plupart des équipes cessent de maintenir leur propre collecteur. Pour le câblage complet des headers et de l'endpoint, voir [comment configurer le Reporting API du navigateur](/fr/blog/how-to-set-up-the-reporting-api).

## Sources [#sources]

* [MDN, header Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
* [MDN, Reporting API](https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API)
* [W3C, Content Security Policy Level 3](https://www.w3.org/TR/CSP3/)
* [W3C, Reporting API](https://www.w3.org/TR/reporting-1/)
* [Dropbox engineering, sur le reporting et le filtrage CSP (bruit des extensions)](https://dropbox.tech/security/on-csp-reporting-and-filtering)

## Articles liés [#articles-liés]

* [Comment configurer le Reporting API du navigateur](/fr/blog/how-to-set-up-the-reporting-api)
* [report-uri vs report-to](/fr/blog/report-uri-vs-report-to)
* [Démarrer avec le reporting CSP](/fr/blog/get-started-csp-reporting)
* [Choisir un endpoint de la Reporting API](/fr/docs/web-security/reporting-api/get-started/choose-an-endpoint)
