Skip to content

About

Free, open source whistleblowing software (Hinweisgebersystem) for EU Directive 2019/1937 and the German HinSchG: self-hosted, anonymous, Docker.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Β 
Β 

Latest commit

Β 

History

893 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

OpenWhistle

OpenWhistle Logo

CI Docker Build Coverage License Version Python FastAPI PostgreSQL Redis Docker Pulls CodeQL GitHub Sponsors

"Speaking up takes courage. Staying silent shouldn't be the safer option."

A secure, self-hosted internal reporting channel β€” because every employee deserves protection.

πŸ” Overview

OpenWhistle is a self-hosted whistleblower platform that fulfils the mandatory reporting channel requirements under the German Hinweisgeberschutzgesetz (HinSchG) and EU Directive 2019/1937. Any company with 50 or more employees and all public authorities are legally required to provide an internal reporting channel. OpenWhistle provides a fully open source solution β€” free of charge, zero vendor lock-in, and privacy-first by design.

🌐 Live demo: demo.openwhistle.net πŸ“– Documentation: openwhistle.net/en/docs/


✨ Features

  • Full anonymity β€” No IP addresses logged at any layer. An employee submitting from the office network leaves no trace. Times the reporter causes (submission, their messages, their files) are stored as the day only, so no exact time can be matched to who was at their desk.
  • Two-factor whistleblower access β€” Case number + secret UUID4 PIN with brute-force protection. No accounts, no email β€” nothing to tie the report back to a person.
  • Multi-step submission wizard β€” Guided 5–6 step form with back/next navigation and Redis-backed session state. Anonymous or confidential mode selectable at step 1.
  • Anonymous / confidential mode β€” Anonymous leaves no personal data. Confidential encrypts name, contact info, and optional secure email with Fernet.
  • Identity on request β€” a confidential reporter's name is hidden by default and shown only to the case handler (an admin of the case's organisation while it is unassigned), after a reason that the audit log records.
  • Multi-location / branch selection β€” Optional location selector shown when the operator has configured active branches or offices; full admin management UI included.
  • Bidirectional communication β€” Required by HinSchG Β§17. The whistleblower can reply to admin messages using only their case number and PIN.
  • HinSchG SLA tracking β€” 7-day acknowledgement and 3-month feedback deadlines with days remaining shown in both the admin dashboard and the whistleblower status page.
  • Role-based access control β€” ADMIN and CASE_MANAGER roles. Case managers can process their assigned reports; only admins manage users, categories, and deletions. The admin sidebar shows each role only the pages it may open.
  • Case assignment β€” Assign reports to any active staff member; "My Cases" dashboard filter for case managers.
  • Status workflow β€” received β†’ in_review β†’ pending_feedback β†’ closed; only valid transitions allowed server-side.
  • 4-eyes deletion β€” Hard deletion requires two different admins (request + confirm); the same admin, or an account one of them made, gets HTTP 409. GDPR Art. 17 compliant.
  • Immutable audit log β€” Every admin action recorded with timestamp and username, shown as readable labels in all four languages; CSV export keeps the machine codes; required by HinSchG Β§11 Abs. 5.
  • Search by case number or content β€” Find a case by any part of its number or by a word in its description or messages. Content is decrypted in memory for that request only; no searchable index is stored, and the confidential name never matches.
  • Internal notes β€” Admin-only notes on cases; never visible to the whistleblower.
  • Case linking β€” Link related cases with bidirectional normalization constraint.
  • Custom categories β€” DB-driven report categories; full management UI at /admin/categories.
  • PDF export β€” Case export including SLA compliance section (HinSchG Β§17); the confidential identity is left out by default and only included via the same audited reveal as the case page.
  • Dashboard statistics β€” SLA compliance rate, status distribution, category breakdown.
  • "Signal" design system β€” documented, token-driven identity (DESIGN.md); app + site, light + dark.
  • Mandatory MFA β€” TOTP (compatible with any authenticator app) required for every admin account. No exceptions, no bypass.
  • Own password, own account β€” every admin changes their password on /admin/account with the current password and a TOTP code; every other session ends. A password an admin, a superadmin reset or the host set must be replaced before any other admin page opens.
  • Self-service recovery β€” admins link their own single sign-on identity; a superadmin resets a lost authenticator, and the host's script resets any account, the last superadmin included.
  • Lockout-proof whistleblower login β€” a correct case number and PIN always open the case; wrong guesses are counted but can never lock the rightful whistleblower out.
  • Password-spraying alarm β€” failed admin logins are counted instance-wide (no username, no IP); crossing ADMIN_FAILED_LOGIN_ALERT_THRESHOLD sends one alert and writes an audit entry.
  • Object-level authorization β€” Every report endpoint enforces per-record access: case managers see only their assigned reports, admins are scoped to their organisation. Role assignment enforces privilege tiers (only superadmins grant superadmin; no self-role-change; the last admin cannot be demoted away).
  • Hardened HTTP security β€” Strict, per-response nonce-based Content-Security-Policy (no unsafe-inline), consolidated single-source security headers (HSTS, X-Frame-Options, nosniff), and strict username validation.
  • Absolute admin session lifetime β€” An admin session can be kept alive by refreshing it, but never past SESSION_MAX_HOURS (default 12) from login; the refresh endpoint itself requires a CSRF token, not just a valid cookie.
  • OIDC / SSO support β€” Optional single sign-on via any OpenID Connect provider (Keycloak, Authentik, Azure AD, Google, …), with PKCE and a verified ID token (signature, issuer, audience, expiry, nonce). Each admin links their own account while signed in; TOTP is still required. LDAP supports LDAPS and StartTLS (LDAP_START_TLS).
  • Authenticator recovery β€” a superadmin resets a lost authenticator on /admin/users (the account gets a new temporary password and enrols a new app at the next login), or the operator runs scripts/reset_admin_password.py --reset-totp <username>. Both end the account's sessions.
  • File attachments β€” Whistleblowers can attach evidence files (PDF, images, .docx, .xlsx, CSV, TXT β€” up to 10 MB each, 5 per report; legacy .doc/.xls are refused, since their author cannot be removed). Identifying metadata (photo GPS/EXIF, PDF and Office author fields) is removed on upload, and files are encrypted with the report's own key.
  • Optional virus scanning β€” Attachments can be checked against a ClamAV clamd daemon before they are stored (CLAMAV_HOST). Fail-closed: if the scanner is unreachable, the upload is refused rather than stored unscanned.
  • Internationalisation β€” English, German, French and Brazilian Portuguese UI; language picker in the nav bar; a test keeps every key and placeholder present in every locale.
  • WCAG 2.1 AA β€” Skip-to-content link, ARIA labels, live regions, visible focus indicators, and keyboard-accessible language picker.
  • Setup wizard β€” Web-based first-run wizard creates the initial admin account with TOTP setup. No manual database steps. It asks for a one-time setup token (SETUP_TOKEN, or a random one logged at first start), so reaching /setup first is not enough to own the installation.
  • Encrypted second factor β€” TOTP secrets are stored encrypted; a database dump alone yields no account's second factor.
  • Hardened containers β€” read-only root file system, no capabilities, no-new-privileges, the image's base images and the bundled nginx and ClamAV pinned by digest (PostgreSQL and Redis by tag), no curl in the image; the Helm chart sets the same.
  • IP leakage detection β€” The admin dashboard warns when upstream proxies forward IP headers.
  • Hard deletion β€” Reports can be permanently deleted including all messages, attachments, and Redis session data. DSGVO-compliant.
  • DSGVO compliant β€” All resources are self-hosted. No external CDN calls, no tracking.
  • Multi-registry Docker β€” Published to GHCR, Docker Hub, and Quay.io on every release.
  • Health-check v2 β€” /health reports database and Redis status; suitable for Kubernetes liveness and readiness probes.
  • Version & update check β€” the admin System page shows the installed version and, when UPDATE_CHECK_ENABLED=true, whether a newer release is available on GitHub. Opt-in and off by default; a daily background job caches the result and no instance data is sent out.
  • Voluntary installation count β€” off unless an admin agrees in the setup wizard or on the System page: once a day a random identifier and the version go to telemetry.wdkro.de, nothing else. TELEMETRY_ENABLED=false locks it off; the demo is never counted.
  • Structured JSON logging β€” LOG_FORMAT=json produces structured log output for aggregation pipelines; LOG_FORMAT=text for human-readable development output.
  • Slack / Teams webhooks β€” NOTIFY_WEBHOOK_TYPE selects Block Kit (Slack) or Adaptive Card (Teams) payload formats so no custom integration work is needed.
  • SLA reminders β€” Background scheduler automatically sends reminders when the 7-day and 3-month HinSchG deadlines approach; Redis dedup keys prevent duplicate notifications.
  • S3-compatible storage β€” Optional STORAGE_BACKEND=s3 routes new attachments to any S3-compatible bucket (AWS, MinIO, Hetzner Object Storage) instead of PostgreSQL BLOBs.
  • LDAP / Active Directory login β€” Admin accounts can authenticate via corporate LDAP; first login auto-provisions the user; TOTP enrollment still required. LDAP uses python-ldap (OpenLDAP client); installing from source needs libldap2-dev libsasl2-dev and pip install '.[ldap,s3]'. The container image includes both.
  • Helm chart β€” Official charts/openwhistle/ Helm chart for Kubernetes deployments.
  • Ansible role β€” Official ansible/roles/openwhistle/ Ansible role for bare-metal / VM deployments with Docker CE, systemd unit, and optional Certbot TLS.
  • Encrypted report storage β€” All report descriptions and messages are encrypted at-rest using per-report envelope encryption (HKDF-SHA256 MEK + Fernet DEK); the key is never stored in the database; pre-encryption rows are transparently readable (backward compat).
  • Separate encryption key with rotation β€” ENCRYPTION_KEY is the root of at-rest encryption, independent of the session-signing SECRET_KEY; old keys stay readable via ENCRYPTION_KEY_PREVIOUS while scripts/rotate_encryption_key.py re-encrypts under the new one.
  • Data retention (GDPR / HinSchG) β€” on by default (RETENTION_ENABLED): closed reports are deleted RETENTION_DAYS days after closure (default 1095 = 3 years); satisfies GDPR Art. 5(1)(e) and HinSchG Β§11 Abs. 5; each deletion recorded in the audit log.
  • Batched notifications β€” new reports and whistleblower messages are announced in one digest every NOTIFICATION_BATCH_MINUTES (default 1440, once a day), so the notice's timing cannot be matched to who was at their desk, and is no more precise than the stored day. Webhooks (Slack, Teams, generic) carry counts only; the email to your own admins also names the case numbers.
  • Encrypted attachment names and drafts β€” filenames are encrypted with the report key; a submission draft in Redis is encrypted with a key held only in the whistleblower's cookie; Office comment and tracked-change authors are anonymised on upload.
  • Multi-tenancy β€” MULTI_TENANCY_ENABLED=true lets a single deployment serve multiple independent organisations with isolated data, per-tenant categories, locations, and users.
  • Per-organisation reporting link β€” with multi-tenancy, each organisation's employees report at /submit/<org-slug>, which offers only that organisation's categories and locations and files the report under it. Admins copy the link from their dashboard.
  • Superadmin role β€” New superadmin role above admin for managing organisations in multi-tenant deployments; existing admin permissions are unchanged.
  • Telephone channel compliance guide β€” Admin page (/admin/telephone-channel) provides a HinSchG Β§ 16 Abs. 3 checklist (oral and text form), implementation options, and the Β§ 11 Abs. 2 rule that a call is recorded only with consent.
  • TLS on by default β€” the bundled nginx in docker-compose.prod.yml serves HTTPS out of the box: copy fullchain.pem/privkey.pem into nginx/certs/ (no symlinks; key root-owned 0600 or 0644), or a self-signed certificate for TLS_HOSTNAME is generated on first start; plain HTTP only redirects. The self-signed certificate is for first boot/testing only β€” install a real one before real users arrive, since the app's HSTS header pins a browser that clicked through the warning. To renew, drop the new certificate into nginx/certs/ and run docker compose up -d tls-init nginx (tls-init is one-shot; restarting nginx alone keeps the old certificate). Behind an external TLS terminator, docker-compose.behind-proxy.yml makes nginx proxy plain HTTP on port 80.
  • Tor onion address β€” ONION_LOCATION adds an Onion-Location header (Tor Browser offers to switch) and a note on the submit page for reporters on a monitored network; see the documentation "Offering an onion address".

🎭 Live Demo

A live demo is available at demo.openwhistle.net

Role Username Password TOTP Code
Admin demo demo 000000

Demo case numbers and PINs are shown after logging in to the demo admin account. The demo resets automatically every 6 hours.


πŸš€ Quick Start

git clone https://github.com/openwhistle/OpenWhistle.git
cd OpenWhistle
cp .env.example .env        # SECRET_KEY, ENCRYPTION_KEY: openssl rand -hex 32 each (fresh install)
docker compose up -d
docker compose logs app | grep "Setup token"  # read the one-time setup token
# Open http://localhost:4009/setup to create the first admin account

Accessing from other devices on the same network? Add SECURE_COOKIES=false to your .env (or docker-compose.yml) when the app is served over plain HTTP. Browsers refuse to send Secure cookies over HTTP, causing session failures on remote devices. Always keep SECURE_COOKIES=true (the default) behind HTTPS in production.

For full installation instructions, environment variable reference, reverse proxy configuration, and administration guide, see openwhistle.net/en/docs/.


πŸ“¦ Container Images

Pre-built multi-arch images (linux/amd64, linux/arm64) are published to three registries:

Registry Image
GitHub Container Registry ghcr.io/openwhistle/openwhistle
Docker Hub kermit1337/openwhistle
Quay.io quay.io/jp1337/openwhistle

All GHCR images are signed with Cosign (keyless, Sigstore).


βš–οΈ Legal Reference

OpenWhistle is designed to comply with:

Disclaimer: OpenWhistle is a technical tool. Operators are responsible for ensuring their deployment meets all applicable legal requirements in their jurisdiction.


🀝 Contributing

Contributions are welcome. Please open an issue before submitting a pull request, and read CONTRIBUTING.md first.


πŸ“œ License

OpenWhistle is released under the GNU General Public License v3.0.


πŸ’› Support

OpenWhistle is developed in free time. If you find it useful, consider supporting the project.

GitHub Sponsors

About

Free, open source whistleblowing software (Hinweisgebersystem) for EU Directive 2019/1937 and the German HinSchG: self-hosted, anonymous, Docker.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages