This project is about handling all the reports browsers (Content Security Policy, Network Error Logging etc.) and e-mail servers (DMARC, SMTP TLS etc.) can send nowadays.
To do that, this project contains a webserver, that will listen to incoming reports, validate them, filter them, structure them and log them to a file. This log file can be read by your log monitoring tools like an ELK-stack or Grafana Loki. With that, you can generate diagrams, configure alerts, you name it.
flowchart LR
browser("Browser") e1@-- CSP, NEL, Permission etc. reports ---> webserver
mailserver("E-Mail server") e2@-- SMTP TLS reports --> webserver
mailserver e3@-- DMARC reports --> mailbox("Mailbox")
mailbox e4@--> imap
subgraph "network-journal"
webserver("Webserver") e11@--> processing("Processing
(filter, derive etc.)")
imap("IMAP client") e12@--> processing
processing e13@--> logfile("Log file")
end
subgraph "ELK-stack/Grafana Loki/..."
logfile e21@--> monitoring("Log file
parser")
monitoring e22@--> visualization("Visualization")
monitoring e23@--> alerting("Alerting")
end
e1@{ animation: slow }
e2@{ animation: slow }
e3@{ animation: slow }
e4@{ animation: slow }
e11@{ animation: slow }
e12@{ animation: slow }
e13@{ animation: slow }
e21@{ animation: slow }
e22@{ animation: slow }
e23@{ animation: slow }
- COEP (MDN)
- COOP (MDN)
- Crash Reports (in a context of websites)
- Content Security Policy (Level 1, 2 and 3) reports (MDN)
- Deprecations (in a context of websites)
- Network Error Logging (MDN)
- SMTP TLS Reports
- DMARC aggregate reports
- Permissions Policy
- Integrity Policy
- Intervention Reports
- Webserver listening to incoming reports
- Report validation
- Filtering by your own domains to prevent spam
- Derive additional metrics from...
- user agent (browser name and version, OS name and version etc.)
- origin/document URLs (host, path, query)
- Log reports to file
- Build from source
- Provide systemd service file
- RPM package
- DEB package
Run the executable once (with the --config
parameter set to a path of your liking) to generate the default configuration file.
Note: Some reporters require TLS to be enabled. If you are using some reverse proxy on the other hand, you do not need to enable TLS in this context but on your proxy.
In the following, network-journal.example.com
needs to be replaced with your network-journal domain while example.com
needs to be replaced with your frontend or e-mail domain respectively.
Note: All Reporting-Endpoints
headers discussed below should be combined into one like so Reporting-Endpoints: crash-reporting="...", "csp-endpoint="..."
or even Reporting-Endpoints: default="https://network-journal.example.com/reporting-api"
. The same should be done for the Report-To
header like so Report-To: {"group": ...}, {"group": ...}
.
Add the following HTTP headers to your HTTP responses:
Cross-Origin-Embedder-Policy: [...]; report-to="coep"
Report-To: {"group": "coep", "max_age": 2592000, "endpoints": [{ "url": "https://network-journal.example.com/reporting-api" }]}
Add the following HTTP headers to your HTTP responses:
Cross-Origin-Opener-Policy: [...]; report-to="coop"
Report-To: {"group": "coop", "max_age": 2592000, "endpoints": [{ "url": "https://network-journal.example.com/reporting-api" }]}
Add the following HTTP header to your HTTP responses:
Reporting-Endpoints: crash-reporting="https://network-journal.example.com/reporting-api"
Add the following HTTP headers to your HTTP responses:
Reporting-Endpoints: csp-endpoint="https://network-journal.example.com/reporting-api"
Content-Security-Policy: [...]; report-to csp-endpoint
Sincereport-to
is not yet supported by all browsers, you probably should do the following instead:Content-Security-Policy: [...]; report-to csp-endpoint; report-uri https://network-journal.example.com/csp
Add the following HTTP header to your HTTP responses:
Reporting-Endpoints: default="https://network-journal.example.com/reporting-api"
Note: At time of writing, deprecation reports are always delivered to the "default" endpoint.
Add a DMARC DNS entry with a rua
tag to send aggregate reports to some mailbox (it is recommended to create a mailbox solely for this purpose).
Set the credentials for this mailbox in the configuration file.
Add the following HTTP header to your HTTP responses:
Reporting-Endpoints: integrity-endpoint="https://network-journal.example.com/reporting-api"
Note: You could also define a different endpoint and link it to your Integrity-Policy
header using the Integrity-Policy: blocked-destinations=..., endpoints="my-integrity-endpoint"
syntax.
Add the following HTTP header to your HTTP responses:
Reporting-Endpoints: default="https://network-journal.example.com/reporting-api"
Note: At time of writing, intervention reports are always delivered to the "default" endpoint.
Add the following HTTP headers to your HTTP responses:
Report-To: { "group": "nel", "max_age": 31556952, "endpoints": [{ "url": "https://network-journal.example.com/nel" }]}
(deprecated) orReporting-Endpoints: nel="https://network-journal.example.com/reporting-api"
(not yet supported by all browsers)NEL: { "report_to": "nel", "max_age": 31536000, "include_subdomains": true }
Add the following DNS entry for your domain:
_smtp._tls.example.com IN TXT "v=TLSRPTv1; rua=https://network-journal.example.com/tlsrpt"
The received reports are logged in the following format:
2025-08-06T14:15:16.123Z INFO [network-journal::<module>] <report_type> <report-content-as-json>
where <report_type>
can be one of:
- COEP
- COOP
- Crash
- CSP
- CSP-Hash
- Deprecation
- DMARC
- IntegrityViolation
- Intervention
- NEL
- PermissionsPolicyViolation
- SMTP-TLS-RPT
and where <report-content-as-json>
looks like this (using a CSP level 3 report as an example here):
{
"report": {
"age": 53531,
"body": {
"blockedURL": "inline",
"columnNumber": 39,
"disposition": "enforce",
"documentURL": "https://example.com/csp-report",
"effectiveDirective": "script-src-elem",
"lineNumber": 121,
"originalPolicy": "default-src 'self'; report-to csp-endpoint-name",
"referrer": "https://www.google.com/",
"sample": "console.log(\"lo\")",
"sourceFile": "https://example.com/csp-report",
"statusCode": 200
},
"type": "csp-violation",
"url": "https://example.com/csp-report",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
},
"derived": {
"client": {
"family": "Chrome",
"major": 127,
"minor": 0,
"patch": 0,
"patch_minor": 0
},
"os": {
"family": "Windows",
"major": 10,
"minor": 0
},
"device": {
"family": "other"
},
"url": {
"host": "example.com",
"path": "/csp-report",
"query": ""
}
}
}
All reports are logged at the INFO
level. If you observe relevant log entries e.g. at the DEBUG
(payload validation errors are logged by actix at this level) or ERROR
, please let me know by filing an issue on GitHub.
This project is licensed under the GPLv3.0 license.