The four anonymity layers

Architecture: a browser reaches nginx over TLS on 443 (80 redirects), Tor Browser reaches the onion listener on 8080, bound to 127.0.0.1. Both lead to the stateless FastAPI app, which needs PostgreSQL and Redis, can use ClamAV and S3, and makes two opt-in outbound requests: the GitHub update check and the telemetry.wdkro.de count. Architecture: a browser reaches nginx over TLS on 443 (80 redirects), Tor Browser reaches the onion listener on 8080, bound to 127.0.0.1. Both lead to the stateless FastAPI app, which needs PostgreSQL and Redis, can use ClamAV and S3, and makes two opt-in outbound requests: the GitHub update check and the telemetry.wdkro.de count.

Defence in depth for anonymity: four independent layers keep IP addresses out of persistent storage.

nginx — Strip the client-IP headers
X-Forwarded-For, X-Real-IP, Forwarded, X-Client-IP, X-Cluster-Client-IP, True-Client-IP and CF-Connecting-IP are cleared before a request reaches the app, even when a CDN added them. access_log off: request URIs never reach the disk. A reverse proxy of your own in front must not log them either.
Application middleware — Drop remote address
The first middleware removes those headers again and clears the peer address, before any route handler runs. No handler can read the real address, even if it tried. If such a header arrives anyway, the admin dashboard warns that a proxy in front knows the addresses.
Database schema — No IP column
No table has an IP address column: not reports, report_messages, audit_log or admin_users. The ORM cannot persist an address; that is structure, not policy.
Redis sessions — No identifying metadata
A whistleblower's status session is a random 256-bit token. Its Redis value is only the report's id, and it expires after 2 hours. No IP, no User-Agent, no browser fingerprint.

Edit this page on GitHub