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
external_webserver("Webserver") e5@<-- active verification of TLS server certificate validity ---> http
subgraph "network-journal"
webserver("Webserver") e11@--> processing("Processing
(filter, derive etc.)")
imap("IMAP client") e12@--> processing
http("HTTP client") e13@--> processing
processing e14@--> 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 }
e5@{ animation: slow }
e11@{ animation: slow }
e12@{ animation: slow }
e13@{ animation: slow }
e14@{ animation: slow }
e21@{ animation: slow }
e22@{ animation: slow }
e23@{ animation: slow }
The visualized log file could look like this (example visualized using Grafana):
- 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
- TLS Server Certificate validity check (expiration and revokation)
- 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 and repository
- DEB package
There are several installation methods available:
- Enable COPR repository:
sudo dnf copr enable cracksalad/network-journal - Install by running
sudo dnf install network-journal - Configure by editing
/etc/network-journal/network-journal.yml - Run
sudo systemctl enable --now network-journalto start the server
- Download the latest RPM package from the releases section on GitHub. Currently, RPM packages are available for el9 and x86_64 architecture only. If you need a package for another version/arch, please file an issue.
- Install it using
sudo dnf install network-journal-*.el9.x86_64.rpm - Configure by editing
/etc/network-journal/network-journal.yml - Run
sudo systemctl enable --now network-journalto start the server
- Download the latest binary from the releases section on GitHub. Currently, binaries are available for Linux and x86_64 architecture only. If you need one for another OS/architecture, please file an issue.
- Move the binary to a path of your liking e.g.
mv network-journal-*-linux-x86_64 /usr/local/bin/network-journal
- Install the Rust toolchain: Install Rust
- Clone the repository
git clone https://github.com/nerou42/network-journal.git && cd network-journal - Run
cargo build -r(cargois part of the Rust toolchain) - Move the executable to a path of your liking e.g.
mv target/release/network-journal /usr/local/bin/
Download the reference configuration file and edit it to match your needs.
Then start the server with the --config option set to the path to your configuration file: network-journal --config /etc/network-journal.yml
β 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-endpointSincereport-tois 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"
Since this is an active verification process, you just have to configure your domains (and ports) to check in the configuration file, like so:
certificate_check:
domains:
- domain: example.com
port: 8443 # defaults to 443
- domain: example.orgβ Note: This check is not comparable to something like SSL Labs > SSL Server Test or Test TLS at all! This check just looks at the not_before and not_after properties of the certificate as well as the included CRL distribution points and verifies, that the certificate is not revoked.
Foreign domains might unexpectedly send reports to you. To reduce spam, you can set a filter to whitelist your own domains, like so:
filter:
domain_whitelist:
- example.com
- domain: example.org
include_subdomains: true # defaults to falseThe 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
- TLS-Certificate-Validity
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.

