Security
Everything in an event is untrusted: it came from your users’ browsers and requests. OpenFault scrubs it before storing it and fences it on the way out.
Scrubbing on ingest
Before anything is written:
- Keys. Any key matching
password,passwd,secret,token,api_key/api-key/apikey,authorization,cookie,set-cookie,session,credit,card,ssnorprivate(case-insensitive, anywhere in the event, including header pairs and query strings) has its value replaced with[Filtered]. - Values. Every string is checked for bearer tokens, AWS access keys, GitHub tokens, Stripe keys, and card numbers that pass the Luhn check.
- IP addresses.
user.ip_addressis dropped unless the project opts in (Settings → Projects).
Raw payloads are never logged.
What is stored
In /data/openfault.db (SQLite):
- Scrubbed events, compressed, for the project’s retention (default 90 days), sampled as in Issues.
- Issues, hourly counters, releases, activity and comments.
- Users with Argon2 password hashes. Sessions, API tokens, invite and pairing codes are stored only as hashes.
- Webhook URLs and secrets, device push tokens, source map caches.
Output
In the REST API, MCP and live stream, event strings are control-character stripped and length-capped. In MCP they sit inside a fenced block marked as untrusted, with fence markers in the data broken up so it cannot escape the block. Event detail (frames, breadcrumbs, request) is withheld from tokens until a person approves the issue for fixing. The web UI autoescapes everything.
SSRF
OpenFault makes outbound requests only for webhooks, email and the push relay. It never fetches source maps from URLs.
Webhooks:
https://only.- No localhost and no private, loopback, link-local, multicast or reserved addresses, including IPv4-mapped and NAT64 IPv6 forms and odd IPv4 spellings like
0x7f.1. - Hostnames are checked again after DNS, at connect time, so DNS pointing or rebinding to a private IP fails.
- No redirects, 5-second timeout.
OPENFAULT_ALLOW_PRIVATE_WEBHOOKS=1 lifts the address rules (and allows plain http:// to loopback). Note: an HTTP(S)_PROXY variable bypasses the DNS check, since the proxy resolves the host.
Rate limits
- Ingest: 1,000 events per 5 minutes and 5,000 per hour per project;
429withRetry-After: 60past that or when the queue is full. - Login: 10 attempts per IP per 15 minutes.
- Pairing claims: 10 failed codes per IP per 15 minutes.
- Live streams: 256 connections per server.
Behind a proxy, set OPENFAULT_TRUST_PROXY=1 so the limits see the client’s IP from X-Forwarded-For. Do not set it without a proxy that overwrites that header.
Sessions
Session cookies are HttpOnly and SameSite=Lax, and Secure when OPENFAULT_BASE_URL is https://. Sessions last 30 days and are revocable. Cookie-authenticated writes have a cross-site request check.
Reporting a vulnerability
Please do not open a public issue. Open a private security advisory on GitHub with the details and steps to reproduce. We will reply there.