Skip to content

jacobbednarz/go-csp-collector

Repository files navigation

This is a content security policy (CSP) violation and network error logging (NEL) collector written in Golang.

It has been designed to listen on port 8080 and accept POST payloads containing the violation report. It captures the report and will write it to STDOUT via Go's logger.

A neat little feature of this tool is that it automatically ignores unactionable reports. Check out the default list if you're interested.

Installation

$ go get github.com/jacobbednarz/go-csp-collector

Alternatively, you can download the binaries from the release page.

Running

$ go build -o csp_collector main.go
$ ./csp_collector

Endpoints

CSP

  • POST /: accepts a CSP violation report (recommended to use /csp for future proofing though).
  • POST /csp: accepts a CSP violation report.
  • POST /csp/report-only: same as /csp but appends a report_only=true attribute to the log line. Helpful if you have enforced and report-only violations and wish to separate them.
  • OPTIONS /reporting-api/csp: CORS preflight handler for the Reporting API.
  • POST /reporting-api/csp: Implementation of the browser Reporting API (w3c / MDN) for CSP violations.

NEL (Network Error Logging)

Important

Network error logging is still experimental in most browsers. This functionality is best effort and may need to adjust as the specification does.

  • POST /nel: accepts NEL reports for an enforced NEL policy.
  • POST /nel/report-only: same as /nel but appends a report_only=true attribute to the log line. Useful when testing a NEL policy before enforcing it.

NEL reports are delivered by the browser via the Reporting API as a JSON array. Each entry with "type": "network-error" is logged; other types in a mixed batch are silently skipped.

To enable NEL reporting, send the NEL and Report-To (or Reporting-Endpoints) headers from your server:

Reporting-Endpoints: nel-endpoint="https://collector.example.com/nel"
NEL: {"report_to": "nel-endpoint", "max_age": 31536000}

Each logged NEL report includes the following fields:

Field Description
report_only true when received on the /nel/report-only endpoint
url The URL of the request that failed
referrer The referrer at the time of the request
type Error type (e.g. tcp.refused, dns.unreachable, ok)
phase Phase of the request: dns, connection, application, or abandoned
protocol Network protocol (e.g. h2, http/1.1)
method HTTP method (e.g. GET, POST)
status_code HTTP status code (0 if the connection never completed)
elapsed_time Duration of the request in milliseconds
server_ip IP address of the server that handled the request
sampling_fraction Fraction of matching requests that were reported
metadata Value of the metadata query parameter (if present)
path Path of the collector endpoint that received the report

Building for Docker

You will either need to build within a docker container for the purpose, or use CGO_ENABLED=0 flag to make the build compatible with alpine linux in a docker container.

$ CGO_ENABLED=0 go build -o csp_collector main.go

Command Line Options

Flag Description
version Shows the version string before exiting
debug Runs in debug mode producing more verbose output
port Port to run on, default 8080
metrics-port Port for the Prometheus metrics endpoint, default 9090
metrics-bind-addr Bind address for the Prometheus metrics endpoint, default 127.0.0.1
filter-file Reads the blocked URI filter list from the specified file. Each line is matched as a prefix against the blocked URI. Note one filter per line.
filter-domains-file Reads a domain block list from the specified file. Each line is a bare domain (e.g. kaspersky-labs.com). A report is dropped when the blocked URI's hostname exactly matches the domain or is any subdomain of it (e.g. gc.kis.v2.scr.kaspersky-labs.com). Matching uses exact suffix comparison — no fuzzy or regex logic. Note one domain per line. Performance note: each check requires a url.Parse call to extract the hostname, which costs roughly 20–35× more than the prefix filter (~180 ns/op vs ~5–8 ns/op). For high-throughput deployments, prefer filter-file for simple prefix matches and only use filter-domains-file where subdomain wildcard matching is genuinely needed.
health-check-path Sets path for health checkers to use, default /_healthcheck
log-client-ip Include a field in the log with the IP delivering the report, or the value of the X-Forwarded-For header, if present.
log-truncated-client-ip Include a field in the log with the truncated IP (to /24 for IPv4, /64 for IPv6) delivering the report, or the value of the X-Forwarded-For header, if present. Conflicts with log-client-ip.
truncate-query-fragment Remove all query strings and fragments (if set) from all URLs transmitted by the client
query-params-metadata Log all query parameters of the report URL as a map in the metadata field

See the sample.filterlist.txt file as an example of the URI prefix filter list, and sample.domainlist.txt as an example of the domain filter list.

Request metadata

Additional information can be attached to each report by adding a metadata url parameter to each report. That value will be copied verbatim into the logged report.

For example a report sent to https://collector.example.com/?metadata=foobar will include field metadata with value foobar.

If query-params-metadata is set, instead all query parameters are logged as a map, e.g. https://collector.example.com/?env=production&mode=enforce will result in "metadata": {"env": "production", "mode": "enforce"} in JSON format, and metadata="map[env:production mode:enforce]" in default format.

report-only mode

Both CSP and NEL have dedicated report-only endpoints (/csp/report-only and /nel/report-only). Reports received on these endpoints are logged identically to their enforced counterparts, but with report_only=true in the log output. This lets you distinguish enforced violations from report-only ones in your log aggregation tool without needing separate collector instances.

Metrics

Prometheus metrics are exposed on a dedicated endpoint:

  • Address: 127.0.0.1:9090 (default)
  • Path: /metrics
  • Full URL: http://127.0.0.1:9090/metrics

The collector exports:

Metric Type Labels Description
csp_collector_reports_total Counter handler, mode Successfully processed CSP or Reporting API reports
csp_collector_nel_reports_total Counter mode Successfully processed NEL reports
csp_collector_reports_filtered_total Counter handler, reason Reports dropped by URI/domain filters
csp_collector_reports_ignored_total Counter handler, reason Reports intentionally ignored (for example unsupported NEL types)
csp_collector_reports_errors_total Counter handler, type Rejected reports (decode or validation failures)
csp_collector_http_request_duration_seconds Histogram handler, route, method, code HTTP request duration for report-ingestion endpoints
csp_collector_http_requests_in_flight Gauge handler, route Active in-flight report-ingestion requests
go_* / process_* Various client-go defaults Runtime and process health metrics

Example Prometheus scrape config:

scrape_configs:
  - job_name: "go-csp-collector"
    static_configs:
      - targets: ["127.0.0.1:9090"]

If you expose metrics on a non-localhost interface using --metrics-bind-addr, protect access with network controls (firewall rules, private network, or service mesh policy).

Output formats

The output format can be controlled by passing --output-format <type> to the executable. Available formats are:

  • Text: A key/value output that quotes all the values. Example: blocked_uri="about:blank" ...
  • JSON: Single line, compressed JSON object. Example: {"blocked_uri":"about:blank"}

The default formatter is text.

Writing to a file instead of just STDOUT

If you'd rather have these violations end up in a file, I suggest just redirecting the output into a file like so:

$ ./csp_collector 2>> /path/to/violations.log

Visualisation

This project purposely doesn't try to solve the problem of visualing the violation data because there are already a bunch of great solutions out there. Once you have your violations being collected, be sure to slurp them into your favourite log aggregation tool.

Deployments

Currently supported deployment mechanisms:

About

A CSP collector written in Golang

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors