Skip to content

icco/reportd

Repository files navigation

reportd

GoDoc Go Report Card

A self-hosted service for collecting browser security reports and Web Vitals, with a dashboard for understanding your site's health.

What it does

reportd receives data from two browser APIs and presents it in a dashboard:

  • Web Vitals (LCP, CLS, INP, FCP, TTFB) -- performance metrics sent by the web-vitals library
  • Browser Reports (CSP violations, deprecation warnings, interventions, crashes, COOP/COEP violations, permissions policy violations) -- sent automatically by browsers via the Reporting API

Data is stored in a SQL database (Postgres or SQLite, for fast dashboard queries via GORM) and BigQuery (for long-term analytics).

Supported report types

Type Source Description
CSP violation report-to / Reporting API Content Security Policy violations
Expect-CT report-to Certificate Transparency violations (legacy)
Deprecation Reporting API Usage of deprecated browser APIs
Permissions Policy Reporting API Blocked access to browser features (camera, geolocation, etc.)
Intervention Reporting API Browser blocked something for performance/UX
Crash Reporting API Tab crash (OOM) or unresponsive page
COEP Reporting API Cross-Origin-Embedder-Policy violations
COOP Reporting API Cross-Origin-Opener-Policy violations
Document Policy Reporting API Document-Policy violations

Unknown report types are stored as raw JSON for forward compatibility.

Setup

Configuration

reportd is configured via environment variables (prefix REPORTD_) or command-line flags:

Variable Flag Required Description
REPORTD_DATABASE_URL --database_url Yes Database connection string (Postgres or SQLite)
REPORTD_PROJECT --project Yes GCP project ID for BigQuery
REPORTD_DATASET --dataset Yes BigQuery dataset name
REPORTD_ANALYTICS_TABLE --analytics_table Yes BigQuery table for Web Vitals
REPORTD_REPORTS_TABLE --reports_table Yes BigQuery table for Report-To data
REPORTD_REPORTS_V2_TABLE --reports_v2_table No BigQuery table for Reporting API v1 data
PORT -- No HTTP port (default: 8080)

Docker

docker run -p 8080:8080 \
  -e REPORTD_DATABASE_URL=postgres://user:pass@host/reportd \
  -e REPORTD_PROJECT=my-gcp-project \
  -e REPORTD_DATASET=reporting \
  -e REPORTD_ANALYTICS_TABLE=analytics \
  -e REPORTD_REPORTS_TABLE=reports \
  -e REPORTD_REPORTS_V2_TABLE=reports_v2 \
  -v /path/to/gcp-service-account.json:/creds.json:ro \
  -e GOOGLE_APPLICATION_CREDENTIALS=/creds.json \
  ghcr.io/icco/reportd

Docker Compose (self-hosting)

The example below runs reportd with its own Postgres database in a single docker compose up -d. BigQuery is required for long-term analytics, so you'll also need a GCP project, a dataset, three tables (analytics, reports, reporting), and a service account JSON key with write access. For SQLite-only usage (no Postgres container), see the note below.

Save this as docker-compose.yml:

services:
  reportd:
    image: ghcr.io/icco/reportd:main
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      REPORTD_DATABASE_URL: postgresql://reportd:reportd@db:5432/reportd?sslmode=disable
      REPORTD_PROJECT: my-gcp-project
      REPORTD_DATASET: reportd
      REPORTD_ANALYTICS_TABLE: analytics
      REPORTD_REPORTS_TABLE: reports
      REPORTD_REPORTS_V2_TABLE: reporting
      GOOGLE_APPLICATION_CREDENTIALS: /creds.json
    volumes:
      - ./gcp-service-account.json:/creds.json:ro
    depends_on:
      - db

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: reportd
      POSTGRES_PASSWORD: reportd
      POSTGRES_DB: reportd
    volumes:
      - reportd-pgdata:/var/lib/postgresql/data

volumes:
  reportd-pgdata:

Then put your GCP service account key next to it as gcp-service-account.json and run:

docker compose up -d

reportd will be reachable at http://localhost:8080. Put it behind a reverse proxy (Caddy, nginx, Traefik) to expose it on a public hostname over HTTPS — browser reporting endpoints must be HTTPS.

To use SQLite instead of Postgres, drop the db service and depends_on, set REPORTD_DATABASE_URL: sqlite:///data/reportd.db, and mount a volume at /data for persistence.

Local development

# Start Postgres
docker run -d --name reportd-pg -p 5432:5432 \
  -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=reportd \
  postgres:17

# Run reportd
export REPORTD_DATABASE_URL=postgres://postgres:postgres@localhost/reportd
export REPORTD_PROJECT=my-project
export REPORTD_DATASET=my-dataset
export REPORTD_ANALYTICS_TABLE=analytics
export REPORTD_REPORTS_TABLE=reports
export REPORTD_REPORTS_V2_TABLE=reports_v2
go run main.go

For SQLite instead of Postgres:

export REPORTD_DATABASE_URL=sqlite:///tmp/reportd.db

Integrating with your site

Web Vitals

Add this snippet to your site to send Web Vitals to reportd:

<script type="module">
  import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'https://unpkg.com/web-vitals@5?module';

  function sendToAnalytics(metric) {
    const body = JSON.stringify(metric);
    (navigator.sendBeacon && navigator.sendBeacon('https://your-reportd-instance/analytics/yoursite', body)) ||
      fetch('https://your-reportd-instance/analytics/yoursite', { body, method: 'POST', keepalive: true });
  }

  onCLS(sendToAnalytics);
  onFCP(sendToAnalytics);
  onINP(sendToAnalytics);
  onLCP(sendToAnalytics);
  onTTFB(sendToAnalytics);
</script>

Browser reports

Add these HTTP headers to your site's responses:

Reporting-Endpoints: default="https://your-reportd-instance/reporting/yoursite"
Content-Security-Policy: ...; report-to default;
Cross-Origin-Opener-Policy: same-origin; report-to="default"
Cross-Origin-Embedder-Policy: require-corp; report-to="default"
Permissions-Policy: camera=(), geolocation=(); report-to="default"

For legacy Report-To support:

Report-To: {"group":"default","max_age":10886400,"endpoints":[{"url":"https://your-reportd-instance/report/yoursite"}]}

API reference

Ingestion (POST)

Endpoint Content-Type Description
POST /analytics/{service} application/json Web Vitals data
POST /report/{service} application/csp-report, application/expect-ct-report+json, application/reports+json Legacy Report-To data
POST /reporting/{service} application/reports+json Reporting API v1 data

Dashboard (GET)

Endpoint Description
GET / Service index with health indicators
GET /view/{service} Dashboard for a specific service
GET /api/vitals/{service} JSON: p75 summaries and daily time series
GET /api/reports/{service} JSON: report counts, recent reports, top violated directives
GET /analytics/{service} JSON: daily average Web Vitals
GET /reports/{service} JSON: daily report counts
GET /services JSON: list of all services
GET /healthz Health check

Dashboard features

The service view page provides:

  • Core Web Vitals cards with p75 values rated against Google's thresholds (good / needs improvement / poor)
  • Time-series charts for each metric with threshold bands
  • Report volume chart showing report counts by type over time
  • Recent CSP violations table with violated directive, blocked URI, document URI, and source location
  • Recent reports table for deprecation warnings, interventions, crashes, and other browser reports
  • Top violated directives bar chart showing the most frequently violated CSP directives

About

A service for receiving CSP reports and others.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors