Release 2.0 is mainly the result of a hardening audit. It found six places where OpenWhistle revealed more than it needed to, or did not keep a promise. All details are in the CHANGELOG.

Update of 27 September 2026: Upgrade to 2.0.1. Until then, the setup wizard created the first account as a plain admin. Only a superadmin can grant superadmin, though, so no installation had one. Organisations and multi-tenancy were therefore out of reach. Migration 008 promotes the first account during the update; check /admin/users afterwards. Details in the changelog.

The PDF that swallowed the reporter's own words

A case report in Polish, Greek or Cyrillic could be exported as a PDF. But the reporting person's text was no longer in it. Every character outside the Latin alphabet, and every typographic quotation mark “like this one”, became a single ? on export. The cause: the built-in PDF font only knows Latin-1. The fix removes the cause: a real Unicode font (DejaVu LGC Sans, Latin/Greek/Cyrillic) is now embedded. Chinese, Japanese, Korean and right-to-left scripts remain open work — with a visible replacement box instead of a silent “?”.

The identity is only revealed on request

Until now, the name and contact details of a confidential report were shown openly on the case page. From 2.0, both stay hidden, on the case page and in the PDF export. They appear only when someone clicks Reveal identity and gives a logged reason of 10 to 500 characters. The reason is stored encrypted in the audit log. Merely opening a case used to leave no trace at all. So every view of a case page now writes its own log entry. Details in the admin guide.

The onion address trusted the wrong header

Offering OpenWhistle through a Tor hidden service is done for whistleblowers on a monitored network. The first version decided “am I the onion service?” from the Host header. But the client sends that header, not the server. A forged Host: anything.onion request could have posed as a Tor visitor. It would have received cookies without the Secure flag and no HSTS at all. Now nginx decides. It sets its own header only in the onion listener and deletes any client value everywhere else. A CI job checks this on every push against a real container. Details in the guide Offering an onion address.

Webhooks now reveal only numbers

The digest webhook for new reports used to send the case numbers themselves, as an array in the payload. It now sends only counts: how many reports, how many messages, plus a sentence built from them. The reminder email to your own admins still names the case number; only the path to the outside was cut back. See Notifications.

A collision crashed half-finished sessions

Case numbers get a random five-digit suffix, which occasionally collides — then the application tries again. For that retry it called a full rollback() of the database session. That invalidates every object already loaded, not just the failed record. The next read of such an object then crashed with MissingGreenlet. It was unpredictable, because it only occurred once the database was already full. The fix: one SAVEPOINT per attempt, which rolls back only the colliding attempt.

The back button that no longer forgets

A file upload field always shows an empty state when you navigate back. If you clicked Back and then Next in the reporting wizard, the application deleted files already uploaded. Every step now answers a submission with a redirect, and a step that does not match is ignored. Attachments are kept until new ones are chosen. Reported and fixed by Zachary Bridges (#94) — many thanks for that.

All six findings in detail

The full technical history is in the CHANGELOG; the live demo shows release 2.0 in action.

Read the CHANGELOG →

More resources: Full documentation · HinSchG compliance guide · GitHub Issues