Delivery · version 3 · 12 October 2026

How the app reaches you, and what that does not protect.

Every encrypted messenger has a moment where you stop checking and start trusting. For Haven, delivered through a browser, that moment is every page load. This page says so in one place rather than leaving you to assemble it from four others.

It is versioned and dated because it will change: the honest answer today is worse than the honest answer we intend to have, and you should be able to tell which one you read.

The short version, and it is not comfortable

A web app has no code signing. When you open Haven, this server sends your browser the JavaScript that does the encryption, and your browser runs it because it came from the right domain — not because anyone verified it is the same code we published. Whoever controls this server, or is in a position to compel whoever controls it, can send one modified bundle to one account and read that account's messages from then on. That is not a flaw in the cryptography. It is upstream of the cryptography, and it applies to every browser-delivered encrypted service, ours included.

The channels

What each way of getting Haven does and does not buy you

In a browser tab  available now

Protects: your messages from us reading them, and from anyone who steals the database. The keys are made in your browser and we never hold them open.

Does not protect: against the server sending you different code. Every load is a fresh act of trust. The app checks what it was served against our signed release log and locks rather than unwrap your keys if that build is missing from it, but a build we publish to everybody passes, and the check is itself part of the code we serve.

Who this is fine for: most people, most of the time. If your adversary would not go as far as compelling a server operator, this is a reasonable trade.

Installed to your home screen  available now

Protects: the same things, plus a service worker caches the app, so you are not fetching fresh code on every single load.

Does not protect: against anything the browser tab does not. The cache updates from the same server without asking you; a new version is held back until the release log holds its digest, which stops an unpublished build and not a published one. Installing it does not make it signed and it is worth being explicit about that, because an icon on a home screen feels like an app and this one is a bookmark with a cache.

Who this is fine for: the same people, with a slightly smaller window of exposure and no new guarantee.

Running it yourself  available now

Protects: you become the server operator, so the trust that was ours is now yours. The unsigned-code problem does not disappear — it moves to somebody you already trust, which for an organisation with its own IT is a real improvement.

Does not protect: anything about the metadata, which your own administrators can then read: the complete membership graph, timestamps, reactions. The list is the same one on this site and it does not shrink because you host it.

Who this is for: newsrooms, unions, NGOs and firms with somebody who can run a server and would rather it were theirs.

A signed native app  NOT AVAILABLE

This is the one that would actually close the gap: an application signed by a key, distributed through a platform store, updating only when a signature verifies, and built reproducibly so a third party can confirm the binary matches the published source.

It does not exist yet. We are not going to describe it in the present tense to make this page read better. When it ships, this page gets a new version number and says so.

Until then, if your threat model includes a party who could compel a server operator, you should be using something else for the parts that matter — and we say which ones on the front page.

What we have actually built

Three things, and none is a solution

Subresource integrity, pinned and enforced at boot. Every third-party script the app loads carries a hash, the server refuses to start if any of them drifts from what was pinned, and there is a test in the suite that fails the build otherwise. That stops a dependency changing under us. It does nothing about our own code, because we are the ones serving it.

A build that can be reproduced, and hashes you can check it against. The optional comment-stripping build is verified three ways, and two builds of it are compared byte for byte on every change, on two different runtimes. Since 29 August 2026 the resulting hashes are published — RELEASE-HASHES.txt in the repository, generated rather than typed, with a gate in the build that fails if the published number stops describing the source. The procedure for checking it yourself is docs/REPRODUCING-THE-BUILD.md: it needs Node and a checkout, no network and no dependencies.

That is still not the claim. The claim would be that somebody outside this project rebuilt a release and got the same bytes, and nobody has. What has happened is smaller and worth stating exactly: two readers who did not write the procedure followed it end to end and reproduced the digest. Both were working for us. That tests whether the instructions are sufficient — which turned out to be a real question, since the first two attempts found fourteen defects between them, including a Windows command that computed the wrong number and would have sent a careful reader to report tampering that had not happened. It is not independent verification and we will not describe it as such.

A check the app runs by itself, against a signed release log. Every bundle the server serves is appended at boot to a signed, append-only log. At launch — before any key is unwrapped, whenever the build changed, and weekly regardless — and before a staged update is allowed to run, your browser hashes what it was actually served and asks the log for proof that this exact digest is in it. If the log cannot be reached, it shows a dismissible "Integrity Verification Offline" banner and carries on, holding any update back. If the log's signed head verifies and the digest is not in it, the app locks before any key is unwrapped and stays locked until it is served a build that verifies. That turns "serve one person a modified bundle" into "publish that bundle's digest where anyone can see it". It is not code signing: an operator willing to publish a malicious digest to everybody passes, and a bundle with the check removed has removed the check.

We would rather list three partial mitigations accurately than one mitigation that sounds complete.

Version history

What this page has said

VersionDateWhat changed
1 28 August 2026 First publication. Browser and installed-PWA delivery available; self-hosting available; signed native application not available. No third party has reproduced a build.
2 29 August 2026 Build hashes now published and machine-checked against the source. A written reproduction procedure was added and followed end to end by two readers who did not write it; both reproduced the digest, and between them found fourteen defects in the instructions, all fixed. Two vendor scripts that are loaded at runtime were found to carry no integrity pin at all and now do. Unchanged: no independent third party has reproduced a release, and no signed native application exists.
3 12 October 2026 Added the check the app now runs by itself against the signed release log: at launch before any key is unwrapped and before an update activates, a dismissible banner when it cannot check, a lock on a confirmed mismatch. The header now carries this page's latest version. Unchanged: no independent third party has reproduced a release, and no signed native application exists.

The full threat model covers everything else — what an operator can read, what a network observer sees, and what a stolen database gives up.