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 composeplugin, not the legacydocker-composebinary) - 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)
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
$ 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.
$ 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
$ 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.
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
-
Create admin account
Enter a username (3–64 characters) and a strong password (minimum 12 characters). -
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.
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-NNNNNformat (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
-
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.
-
Fill in the report. The only required field is the report description. Category, date of incident, and supporting details are optional.
-
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
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.
client.host with
0.0.0.0. No route handler can access the real remote address
even if it tried.
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.
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-Securitywithmax-age=31536000; includeSubDomainsContent-Security-Policyrestricting scripts, styles, and frames to selfX-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: no-referrerPermissions-Policydisabling 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
DEMO_MODE=true
Demo mode behaviour
- Prefilled admin credentials: username
demo, passworddemo, TOTP code000000 - The static TOTP code
000000is 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.netis automatically wiped and restarted every hour via a cron job
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
# 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.
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.