Documentation

Complete reference for installing, configuring, and operating OpenWhistle — the free, self-hosted whistleblower reporting platform compliant with HinSchG and EU Directive 2019/1937.

Overview

OpenWhistle is a stateless, self-hosted web application that provides a secure internal reporting channel as required by the German Hinweisgeberschutzgesetz (HinSchG) and EU Directive 2019/1937. It is released under the GNU General Public License v3.0.

The application is built with Python and FastAPI, backed by PostgreSQL 18 and Redis 8, and distributed as a Docker image. It has no external dependencies at runtime — no CDN calls, no telemetry, no third-party services.

Key design principles

  • Anonymity first: No IP addresses are stored at any layer. The database schema contains no IP column. Redis session keys carry no identifying metadata.
  • Stateless application: All session state lives in Redis. The application container is horizontally scalable and disposable.
  • Compliance by design: §17 HinSchG deadline tracking is built into the data model, not bolted on as a feature.
  • No vendor lock-in: Standard Docker Compose deployment. Export your data at any time using standard PostgreSQL tools.

Supported platforms

  • Any Linux host with Docker 24+ and Docker Compose v2
  • x86_64 and ARM64 (Apple Silicon) architectures
  • Container registries: GitHub Container Registry (ghcr.io), Docker Hub, quay.io

Current version

v0.1.0 — Initial release. See the CHANGELOG for a full list of changes.

Requirements

OpenWhistle is designed to run on minimal infrastructure. A shared VPS with 1 vCPU and 1 GB RAM is sufficient for most organisations.

Software

  • Docker 24.0 or newer
  • Docker Compose v2.0 or newer (the docker compose plugin, not the legacy docker-compose binary)
  • PostgreSQL 18 and Redis 8 are bundled in the Docker Compose configuration — no separate installation needed

Hardware

  • 512 MB RAM minimum (1 GB recommended)
  • 1 vCPU minimum
  • 5 GB disk space (for application, database, and logs)

Network

  • A domain name pointing to your server
  • A valid HTTPS certificate (Let's Encrypt recommended)
  • Port 80 and 443 open inbound (for nginx)
Important — HTTPS required

Operating a whistleblower reporting system over plain HTTP violates the confidentiality obligations of §8 HinSchG and GDPR. HTTPS is non-negotiable in production deployments.

Installation

The recommended installation method uses Docker Compose. This starts all services (application, PostgreSQL, Redis, nginx) with a single command.

Step 1 — Clone the repository

bash
$ git clone https://github.com/openwhistle/OpenWhistle.git
$ cd OpenWhistle

Step 2 — Configure environment

Copy the example environment file and edit it with your settings. At minimum you must set SECRET_KEY, DATABASE_URL, and REDIS_URL.

bash
$ cp .env.example .env
$ nano .env

# Minimum required values:
SECRET_KEY=your-long-random-secret-key-here
DATABASE_URL=postgresql+asyncpg://openwhistle:password@db:5432/openwhistle
REDIS_URL=redis://redis:6379/0

Step 3 — Start services

bash
$ docker compose up -d
► Network openwhistle_default      Created
► Container openwhistle-db-1        Started
► Container openwhistle-redis-1     Started
► Container openwhistle-app-1       Started
► Container openwhistle-nginx-1     Started

Step 4 — Run the setup wizard

Open http://localhost:4009/setup (or your domain if nginx is configured) in a browser. Follow the wizard to create your first admin account and configure TOTP. The wizard disables itself permanently after completion.

Database migrations

OpenWhistle applies Alembic migrations automatically on startup. The application checks for pending migrations at boot and will not serve traffic until all migrations are applied successfully.

Configuration

All configuration is done via environment variables in the .env file. There are no YAML configuration files or database-stored settings (except admin preferences managed through the UI).

Variable Required Description Default
SECRET_KEY Required Cryptographic key for session signing and CSRF tokens. Generate with openssl rand -hex 32.
DATABASE_URL Required PostgreSQL connection string. Must use the async driver. Format: postgresql+asyncpg://user:password@host:port/dbname
REDIS_URL Required Redis connection string. Format: redis://host:port/db
APP_NAME Optional Display name shown in the application UI and browser title. OpenWhistle
DEMO_MODE Optional Set to true to enable demo mode. See Demo Mode section. false
OIDC_ENABLED Optional Set to true to enable OIDC login for administrators. false
OIDC_SERVER_METADATA_URL Optional OIDC provider discovery URL (e.g. https://accounts.google.com/.well-known/openid-configuration). Required when OIDC_ENABLED=true.
OIDC_CLIENT_ID Optional OAuth 2.0 client ID from your OIDC provider. Required when OIDC_ENABLED=true.
OIDC_CLIENT_SECRET Optional OAuth 2.0 client secret from your OIDC provider. Required when OIDC_ENABLED=true.
OIDC_REDIRECT_URI Optional OAuth 2.0 callback URL. Must match the redirect URI registered with your OIDC provider. Example: https://yourdomain.com/admin/oidc/callback. Required when OIDC_ENABLED=true.

First-Run Wizard

On first boot, the /setup route serves a multi-step browser-based wizard. The wizard is automatically disabled once an admin account exists in the database.

Wizard steps

  1. Create admin account
    Enter a username (3–64 characters) and a strong password (minimum 12 characters).
  2. Configure TOTP
    Scan the displayed QR code with an authenticator app (Google Authenticator, Authy, Bitwarden, etc.). Enter the 6-digit code to confirm the secret is correctly registered. TOTP is mandatory and cannot be skipped.
Save your TOTP secret

Write down the TOTP secret key displayed during setup and store it securely offline. There are no backup codes. If you lose access to your authenticator app, you will need to reset the TOTP secret directly in the database.

Admin Guide

The admin portal is accessible at /admin. Login requires your username, password, and a current TOTP code (or OIDC if configured).

Dashboard overview

The dashboard shows all open reports sorted by urgency. Each report card displays:

  • Unique case number in OW-YYYY-NNNNN format (sequential per year)
  • Submission date and time
  • Report category (if provided by the whistleblower)
  • §17 acknowledgement deadline countdown (red when under 24 hours)
  • §17 feedback deadline countdown
  • Unread message indicator

Managing reports

Click any report to open the case view. From here you can:

  • Read the full report content
  • Change the case status (Open, In Progress, Closed)
  • Send a message to the whistleblower via the anonymous channel
  • View the full message thread
  • Close and hard-delete the case (triggers §26 HinSchG data erasure)

HinSchG deadline tracking

The dashboard enforces the statutory timelines from §17 HinSchG:

  • 7-day acknowledgement: The countdown starts from report submission. The dashboard shows a yellow warning at 3 days, red at 24 hours.
  • 3-month feedback: The countdown starts from the date of acknowledgement (§17 Abs. 2). A feedback response must be sent via the message channel before the deadline.

Whistleblower Guide

The whistleblower submission form is accessible at the root URL of your OpenWhistle installation (e.g. https://your-domain.example.com/). No account, email address, or personal information is required.

Submitting a report

  1. Navigate to the submission URL. Use a private browsing window and, where possible, a network that is not traceable to you (public Wi-Fi, Tor, etc.) for maximum protection.
  2. Fill in the report. The only required field is the report description. Category, date of incident, and supporting details are optional.
  3. Submit the form. Immediately note down the case number and PIN displayed on the confirmation screen. These are shown only once and cannot be recovered if lost.

Checking report status

Navigate to /status on the same installation. Enter your case number and PIN. You will see:

  • Current case status
  • Admin responses (if any)
  • A form to send a follow-up message

Security recommendations for whistleblowers

  • Do not submit from a device or network that can be traced to you
  • Use a browser in private/incognito mode
  • Store the case number and PIN in a secure location not connected to your work identity
  • Never share the PIN with anyone

Security Architecture

OpenWhistle is designed with a defence-in-depth approach to anonymity. There are four independent layers that prevent IP addresses from reaching persistent storage.

The four anonymity layers

nginx — Strip X-Forwarded-For
The nginx reverse proxy configuration explicitly strips the X-Forwarded-For, X-Real-IP, and all X-Client-* headers before forwarding requests to the application. Even if a CDN or upstream proxy adds these headers, they are removed at the nginx layer.
Application middleware — Drop remote address
A FastAPI middleware intercepts every request before it reaches any route handler and replaces the client.host with 0.0.0.0. No route handler can access the real remote address even if it tried.
Database schema — No IP column
The reports, messages, and sessions tables have no ip_address column. It is structurally impossible for the ORM to persist an IP address — not just a matter of policy.
Redis sessions — No identifying metadata
Session tokens in Redis are UUID4 values. The session data structure contains only the case number (not the PIN) and an expiry timestamp. No IP, no User-Agent, no browser fingerprint is stored in session data.

Rate limiting without IP tracking

Brute-force protection on the report access endpoint uses Redis-based rate limiting keyed on the session token, not the IP address. A fixed-window counter increments on each failed PIN attempt. After 5 failed attempts within 15 minutes, the session token is invalidated and a new one must be obtained. The whistleblower's IP address is never used as a rate-limiting key.

Security headers

The nginx configuration applies a strict set of HTTP security headers to all responses:

  • Strict-Transport-Security with max-age=31536000; includeSubDomains
  • Content-Security-Policy restricting scripts, styles, and frames to self
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: no-referrer
  • Permissions-Policy disabling geolocation, camera, microphone

OIDC integration

When OIDC is configured, admin login can be completed entirely via the external provider. The application validates the authorization code, fetches the userinfo endpoint, extracts the sub claim, and maps it to a local admin account. A session is created directly — no separate TOTP step is required for OIDC logins. The admin account must be pre-linked to an OIDC identity by a system administrator.

Demo Mode

Demo mode is intended for the public demo instance at demo.openwhistle.net. It should not be used for production deployments.

Enabling demo mode

.env
DEMO_MODE=true

Demo mode behaviour

  • Prefilled admin credentials: username demo, password demo, TOTP code 000000
  • The static TOTP code 000000 is always accepted — no authenticator app needed
  • All reports and messages are visible to the demo admin
  • A banner is displayed on all pages indicating this is a demo instance
  • The demo instance at demo.openwhistle.net is automatically wiped and restarted every hour via a cron job
Never submit real reports to the demo

The demo instance is public and accessible to anyone. Demo data is wiped hourly but may be readable by other users in the interim. Never submit real or sensitive information to the demo instance.

Upgrading

OpenWhistle follows semantic versioning. Minor and patch releases are backward-compatible. Major releases may include breaking changes documented in the CHANGELOG.

Standard upgrade procedure

bash
# Pull the latest image
$ docker compose pull

# Restart containers with the new image
$ docker compose up -d

# Database migrations are applied automatically on startup
$ docker compose logs app | grep migration

Before upgrading

  • Read the CHANGELOG for the target version
  • Back up your PostgreSQL database: docker compose exec db pg_dump -U openwhistle openwhistle > backup.sql
  • Note your current version with docker compose exec app python -m app --version

Rollback

To roll back to a specific version, pin the image tag in docker-compose.yml and re-run docker compose up -d. Database migrations cannot be automatically rolled back — restore from backup if a migration causes issues.

Stay up to date

Watch the GitHub repository for new releases. Security fixes are released as patch versions. Enable GitHub's Dependabot or watch the repository for release notifications.