| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| OpenWhistle 2.1.0 source code.tar.gz | 2026-09-27 | 4.5 MB | |
| OpenWhistle 2.1.0 source code.zip | 2026-09-27 | 4.7 MB | |
| README.md | 2026-09-27 | 15.8 kB | |
| Totals: 3 Items | 9.2 MB | 0 | |
Upgrade notes
- Migration 009 adds
must_change_passwordto every account,falsefor existing ones: nothing records who set their password. - Migration 010 recomputes every feedback deadline by §17 Abs. 2 HinSchG: three calendar months from the acknowledgement or, without one, three months and seven days from receipt. Unacknowledged reports get a deadline for the first time, and their reminders start.
- Migration 011 gives every account an organisation, the default one where it had none.
- Migration 012 records who made each account (
created_by_id), from the audit log. - An unedited
docker-compose.ymlor.env.exampleinstall no longer starts. Compose now reads.env, and no example key passes the 32-character check. SetSECRET_KEYandENCRYPTION_KEY. - Images are published only after CI, E2E and the security scans pass on the tagged commit,
and only for a tag on
main. A release tag withoutDOCKERHUB_TOKENnow fails. latest,XandX.Ymove only to the highest stable release in their line: never to a pre-release, never back to an old tag being re-published.
Added
- Every admin changes their own password, and must when someone else set it. My account
in the sidebar opens
/admin/accountfor every role: username, role, organisation, login methods. A password change (POST /admin/account/password) needs the current password, the new one twice and a current TOTP code, counts wrong guesses towards the sign-in lockout, ends every other session of the account and is audited (auth.password_changed). An account whose password an admin chose on/admin/users, a superadmin reset or the host's reset script set must change it before any other admin page opens; a new or reset account enrols its authenticator first. LDAP and SSO accounts see where to change theirs. InDEMO_MODEthe demo accounts keep theirs. - Admins link their own single sign-on identity. Signed in with password and TOTP, choose
Link single sign-on on
/admin/account(POST /admin/oidc/link); Unlink single sign-on removes it. The link request is bound to that session and to the purpose "link" in Redis, so a login state never links and a link state never signs in. An identity linked to another account is refused. Both are audited (auth.sso_linked,auth.sso_unlinked). - A lost authenticator can be reset. A superadmin clicks Reset authenticator on
/admin/users(POST /admin/users/{id}/reset-totp): the account becomes new again. The old app and the old password stop working, the user's sessions end, and the superadmin sees a random temporary password once, to hand over; with it, the next login enrols a new app at/admin/mfa/setup. The password is in no log and no audit entry. LDAP and SSO accounts keep their first factor. Not for one's own account, and not for the demo accounts inDEMO_MODE. On the host,scripts/reset_admin_password.py --reset-totp <username>prints a new secret andotpauth://URI once, for any account including the last superadmin. Both are audited (admin.totp_reset).
Documentation
- The blog is in English and German. Every article has its twin in the other language,
linked both ways by
hreflangand a language switch; the English index is/blog/en.html, and the English pages link their Blog item there. The German URLs are unchanged.test_every_blog_page_exists_in_english_and_germanholds it for new articles. -
New article: removing metadata without altering the evidence photo, in English and German: what the bug bounty measured, and why no test noticed.
-
A changelog page on the website, rendered from this file (
docs/changelog.html, checked bytests/test_changelog_page.py). - Diagrams and screenshots in the documentation, in the reader's theme: architecture,
submission flow, case lifecycle and login as Mermaid sources rendered to SVG, and the main pages
as screenshots re-taken by
scripts/take_screenshots.py. - The user documentation is rewritten to measured prose limits (no sentence over 30 words, average under 18) and corrected where it contradicted the code: OIDC logins also need TOTP, a correct PIN always opens its report, the app sets the security headers, case numbers are random.
CONTRIBUTING.mdcarries the documentation rules; guard tests hold them.- The installation example now puts the Redis password into
REDIS_URL. The production Redis runs with--requirepass "${REDIS_PASSWORD}", so the documentedredis://redis:6379/0could not connect..env.examplenow pinsOPENWHISTLE_VERSION2.0.1;docs.htmlno longer names the default, which had gone stale at 2.0.0. The retention section says what HinSchG §11 Abs. 5 says: deletion three years after the procedure ends, not a three-year minimum. - Both landing pages are rewritten for "open source whistleblower software" and "kostenloses Hinweisgebersystem". Claims the code or the statute contradict are gone: "100 % HinSchG-konform", rate limits "never IP-based", "OIDC or TOTP", and competitor prices without a source.
- A comparison page,
open-source-whistleblowing-software.html: OpenWhistle, GlobaLeaks, SecureDrop and Hush Line on licence, hosting, deadlines, Tor, languages and audits, each sourced. - A new German article,
blog/interne-meldestelle-kostenlos.html: what a free system still costs. The German comparison article keeps only vendor-published facts, each sourced and dated; the three older articles carried 2025 as publication date, they were published 2026-04-27. - The German pages are re-targeted to the queries Search Console shows. "Hinweisgebersystem
Software", "Hinweisgebersystem Anbieter (Vergleich)" and "HinSchG Software" had about 1,300
impressions and no click in three months, at positions 15 to 29.
de/index.htmlnow leads with "Hinweisgebersystem Software", the comparison article with "Hinweisgebersystem Anbieter Vergleich" and seven selection criteria. A new article,blog/hinweisgebersystem-dsgvo-eu-hosting.html, answers the EU-hosting, encryption and GDPR questions (positions 9 to 10) in its first sentence. Both landing FAQs add the question-style queries. - Every page has a search head: title of at most 60 characters, unique description, canonical,
hreflang, Open Graph, JSON-LD.
docs/sitemap.xmlis rendered from the heads byscripts/render_sitemap.py; missing paths get404.html.tests/test_seo.pyholds all of it. - The share image is real.
og-image.pngwas a blank navy rectangle, so every shared link showed an empty card. The touch icon is now 180×180. - HinSchG citations are corrected and checked by
tests/test_hinschg_citations.py: the pages cited a third Absatz of § 17 and a seventh of § 16, which do not exist; deletion is § 11 Abs. 5, not § 26; a missing reporting office costs up to 20,000 € (§ 40 Abs. 2 Nr. 2, Abs. 6), not 50,000 €.
Security
- Three code-scanning alerts closed before this release. Draft and status-session ids were
checked with
^…$andre.match, and$also matches before a trailing newline; every such check isfullmatchnow (#107). The language switch and draft-cookie alerts were shown not exploitable by tests and dismissed with that evidence. - Every request path and query string reached the container's stdout, the setup token and OIDC
codes included.
--no-access-logempties uvicorn's access handlers; importing the app gave them back. Measured with the Dockerfile's own command:"GET /setup?token=… HTTP/1.1" 200. - One TOTP code opened two sessions. pyotp compares after Unicode normalisation, so
575203(fullwidth) matched a used575203while the single-use key held the raw string. Codes are six ASCII digits; one code authenticates one action, sign-in after enrolment included. - The setup wizard stored any TOTP secret the hidden field sent:
AAAAbecame the first superadmin's second factor,!!!!was a 500. - A plain admin could re-enable a superadmin another superadmin had disabled; in
DEMO_MODEany visitor could deactivate or demote a demo account and lock out everyone after them. - Four eyes were two accounts. An admin could make a second admin, sign in with the password they had just chosen, and confirm their own deletion request. An account and the accounts it made, directly or through others, no longer confirm each other.
- A confirmed deletion left no trace.
report.delete_confirmedhad labels in every locale and was never written, and the report's own entries go with it. It is written now, with case number, requester and confirmer. - Office files kept the Windows account name in the saved-folder path (
absPath), template and link paths (C:\Users\<name>\…),fileSharing, SharePoint columns, revision-session ids and document variables. PDF XMP on pages, images and fonts (dc:creator) and editorPieceInfosurvived; a PDF with embedded files is now refused. A JPEG's motion-photo video or JFIF thumbnail rode along after the image. - A session refreshed during a password change survived it: the refresh stored its new token after the sweep had passed.
- The TLS private key was 0644 until its chmod; it is 0600 from the first byte.
- Quay lost releases 1.3.1, 1.4.0 and 1.5.0. The weekly cleanup deleted old
sha-tags by digest, which deletes every tag on that digest; 2.0.0 was three pushes from the same fate. It now deletes only digests no kept tag uses.
Fixed
- Switching LDAP on locked out every local account, the setup wizard's superadmin included.
A directory user named like a local account was a 500; an entry without
LDAP_ATTR_USERNAMEprovisionedALICEnext toalice. The lockout countedalice,AliceandALICEseparately and expired from the first wrong password: ten spread over 29 minutes locked for one. - The §17 HinSchG deadlines were computed five ways.
+90 daysis a day late for an acknowledgement on 31 January or 1 February; a case moved to "in review" without acknowledging had no feedback deadline and no reminder; 12 hours before the deadline the dashboard said overdue and the case page 0 days left; the PDF called 7 days 12 hours compliant; the 7-day rate counted a report received today as missed. One module computes them now. - Photos and screenshots are stored as taken. A palette PNG came out black, an animated PNG or WebP kept one frame, a 5.5 MB JPEG grew to 14.4 MB, past the limit. JPEG, PNG and WebP are cleaned by dropping metadata segments, pixels untouched; iPhone MPO photos are accepted.
- One upload could stall the server: a 450 KB PNG cost 1.1 GB of RAM, a 518 KB GIF 26 s of CPU. PNG, JPEG and WebP are no longer decoded, GIF and TIFF are capped at 50 megapixels, and cleaning runs off the event loop. A file over 10 MB after cleaning is refused.
- Multi-tenancy could not give an organisation its own admin: a new account took its
creator's organisation. Accounts made before multi-tenancy was switched on, or by LDAP, had none
and then saw no case. A superadmin chooses the organisation on
/admin/users. - Text at its
maxlengthwas refused or cut for its line breaks. Browsers send CRLF;maxlengthcounts one (Chromium: 20 characters sent as 24). An admin reply took any length; a long category slug, location code or organisation name, or a large sort order, was a 500. - A mistyped notification address was stored: the form is
novalidate, sotype=emailchecked nothing, and the mail never came. A reply to a closed case was stored from a page left open; it is refused and the whistleblower told. A lower-case case number was refused. - The audit export ignored the page's filters and stopped at 10 000 rows. Taking it, downloading an attachment and unassigning are now recorded; a superadmin's search is recorded in every organisation whose reports it read.
TZmoved the "03:00 UTC" jobs: withTZ=Europe/Berlinretention ran at 01:00 UTC. The retention page named tomorrow for tonight's run between 00:00 and 03:00.- The digest said "1 new message" for three replies on one case; it counts cases and now says so.
- A non-ASCII CSRF token was a 500, and two
ow_csrfcookies refused every form. - The username fields refused
.,@and spaces the server accepts. - Retention left the status sessions of the cases it deleted in Redis.
- Unlinking SSO was allowed when LDAP had been switched off and the link was the only way in.
- The Quick Start ignored
.env:docker-compose.ymlhard-codedSECRET_KEYand set noENCRYPTION_KEY. - The publish job could call a tag it could not read "verified": a failure inside
for x in $(…)does not tripset -e. - The Ansible role could not finish a TLS install or renew its certificate (nginx held port 80
before the certificate existed), and its
.envchanged values containing$. helm upgradewith changed values restarted no pods, reset the HPA's replicas, and the ingress refused uploads over 1 MB./static/was not rate-limited: 80 parallel requests, 80 × 200; now 49 get 429, as on/.- Helm, Ansible and
.env.exampleshipped the old brand colour#0f4c81. - CI's nginx pin could never be updated: Renovate matched none of the three
docker runpins. -
A test patched
asyncio.create_taskand left aMagicMockin the notification queue, failing later tests depending on order. -
Admin lists hid their actions behind a sideways scrollbar. Users, categories and locations sat in two thirds of the page, as organisations did in 2.0.1: the role select read "Falll" and the categories' actions were out of view at 1920 px. Every list now runs under its form, across the full width, and table cells are 0.75 rem a side, so the German dashboard's eight columns fit 1,064 px.
- OIDC login could never succeed. Nothing ever wrote an account's OIDC
suband issuer, so every SSO login ended in "no account is linked". Linking now exists (see Added). - A lost authenticator locked its admin out for good. The reset script kept the TOTP secret, and the docs said to edit the database. See Added.
- No session is accepted for an account whose authenticator awaits enrolment. A reset takes effect in the same commit, before its sessions are swept from Redis.
- Whoever set a password for someone else knew it for good. No page let an admin change their own password, so the admin who created an account, or the superadmin who reset it, kept knowing it. See Added.
- The host's password reset left the account's sessions running and the audit log empty.
reset_admin_password.py --usernamenow ends every session of the account and recordsadmin.password_reset, never the password. - Four form fields skipped their format check in Chrome. Browsers compile
patternwith thevflag, where a bare-closing a character class is a syntax error; the new-user, location, organisation and setup forms logged it and checked nothing. Now escaped, held bytests/test_pattern_attributes.py. - The sidebar's Log out sat 12 px left of the links above it, and the second panel of a two-column admin page (users, categories, locations) started 20 px below the first.
Removed
/admin/demo/reset. It reset nothing (the seed only adds what is missing) and no page linked to it.app/schemas. No route used it; it looked like the validation the wizard was missing.