---
title: "Collect reports of blocked content · Agent Wranglers"
url: https://agentwranglers.se/playbook/website/security/reporting-endpoints
updated: 2026-10-20
licence: "All rights reserved"
---

# Collect reports of blocked content

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Security](https://agentwranglers.se/playbook/website/security)
- Rule: Name a place for browsers to send policy errors with `Reporting-Endpoints`. Log those reports without any personal data.
- How: Send `Reporting-Endpoints: csp-endpoint="/reports"` and point `report-to csp-endpoint` at it. Also add `report-uri /reports` for browsers without `report-to`. Those send one `application/csp-report` object instead of a list. Accept both types, cap the body at 64 KB and put the endpoint behind the [rate limit](https://agentwranglers.se/playbook/website/security/rate-limits). Log only the type, path and directive, never the full address or the visitor's IP address.
- Check: Break the policy on a test page in two browsers. Each report appears in the log as one line with a path and a directive. A 1 MB POST answers 413.

Example:

```http
Reporting-Endpoints: csp-endpoint="/reports"
Content-Security-Policy: default-src 'self'; report-to csp-endpoint; report-uri /reports
```

```js
// Express: accepts application/reports+json (a list) and application/csp-report (one object)
app.post('/reports', express.json({ type: ['application/reports+json', 'application/csp-report'], limit: '64kb' }), (req, res) => {
  const reports = Array.isArray(req.body) ? req.body : [{ type: 'csp-violation', url: req.body['csp-report']?.['document-uri'], body: { effectiveDirective: req.body['csp-report']?.['effective-directive'] } }];
  for (const r of reports) console.log(r.type, URL.parse(r.url ?? '')?.pathname, r.body?.effectiveDirective);
  res.status(204).end();
});
```

```python
# Flask: same two formats, body capped by MAX_CONTENT_LENGTH
from urllib.parse import urlsplit
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024

@app.post("/reports")
def reports():
    data = request.get_json(force=True, silent=True) or []
    if isinstance(data, dict):
        old = data.get("csp-report", {})
        data = [{"type": "csp-violation", "url": old.get("document-uri", ""), "body": {"effectiveDirective": old.get("effective-directive")}}]
    for report in data:
        print(report.get("type"), urlsplit(report.get("url", "")).path, report.get("body", {}).get("effectiveDirective"))
    return "", 204
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers website playbook: https://agentwranglers.se/playbook/website/security/reporting-endpoints`.

- Previous: [Allow only the scripts you wrote](https://agentwranglers.se/playbook/website/security/content-security-policy)
- Next: [Tell people how to report a hole](https://agentwranglers.se/playbook/website/security/security-txt)
- [Security](https://agentwranglers.se/playbook/website/security)
