Can you trust encryption that runs in a browser?
Updated 11 July 2026
Every browser-based encrypted messenger gets asked this, and most dodge it. Here is the objection at full strength, what Sendant actually does about each part of it, and the honest answer about where the browser's limits are.
Short answer: browser-based end-to-end encryption is genuinely weaker than an installed app in one specific way — the code is re-fetched from a server every visit, so you re-trust the operator every visit. Sendant doesn't pretend otherwise. We treat the browser as the low-friction tier (start chatting in seconds, nothing installed) and the installed Android app as the high-assurance tier (a signed binary that doesn't change under you). Same cryptography in both; different delivery trust.
The case against browser crypto, steelmanned
The classic argument — argued for years on Hacker News and in the "JavaScript cryptography considered harmful" literature — is not a strawman, and we'd rather state it better than our critics do:
1. The server serves the code
An installed app is a fixed, signed artifact. A web app is re-delivered on every page load. If the operator turns malicious, is compromised, or is legally compelled, it can ship a modified script to one specific user, capture their keys or plaintext for that session, and leave no trace on the client. End-to-end encryption where one end's code comes from the adversary's server collapses to trust-the-server.
2. The browser is a hostile runtime
Any cross-site-scripting flaw means attacker code runs inside the security boundary, next to your keys. The DOM, extensions, and third-party scripts are a much larger attack surface than a native process.
3. Extensions see everything
A browser extension with page access can read what you type before it is ever encrypted. No web app can defend against a runtime the user has already extended with untrusted code.
All three are real. Any browser messenger that claims to have "solved" them is overclaiming. What follows is what we do about each — and what we explicitly do not claim.
What Sendant does about it
One crypto implementation, not twoShipped
Sendant's cryptographic core — X3DH key agreement and the Double Ratchet, the same primitives Signal uses — is written once, in Go. The Android app ships it as a compiled native library; the browser runs the same code compiled to WebAssembly. There is no second, hand-written JavaScript crypto implementation to drift, diverge, or quietly weaken. The classic "JavaScript crypto is amateur crypto" objection targets hand-rolled JS primitives; Sendant's browser client contains none.
A strict Content-Security-Policy, live todayShipped
The web app at app.sendant.io serves this policy on every response — you can verify it right now with curl -sI https://app.sendant.io:
content-security-policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval';
style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';
connect-src 'self' https://mbx.sendant.io; worker-src 'self';
object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'
In plain terms: the page can only run scripts from its own origin — no inline script, no third-party scripts, no CDNs, no analytics, no tag managers. It can only talk to itself and the mailbox endpoint. It cannot be framed (no clickjacking), cannot load plugins, cannot submit forms anywhere. Alongside it: HSTS with a one-year preload policy, nosniff, and a no-referrer policy. This is the front-line defense against injected or smuggled script — attack #2 above.
Keys wrapped with a non-extractable keyShipped
Your identity key and session state are not stored as readable plaintext in the browser. They are encrypted at rest with an AES-256-GCM key generated non-extractable inside the browser's WebCrypto engine and held in IndexedDB — a key that, by browser design, can never be exported, even by code running on the same page. Honest scope: this protects against a stolen storage blob (a copied profile directory, a leaked backup, storage-reading malware). It does not protect against malicious code already executing in the page — for that, the CSP above is the defense, and the residual risk is attack #1, which no web app can fully close.
Key-substitution is detectableShipped
Every conversation has a safety number — a SHA-256-based commitment to both parties' full identity key bundles, shown as twelve 5-digit groups in both the app and the web client. If any server ever substituted keys to insert itself into a conversation, the numbers stop matching. Compare them over any channel the server doesn't control (in person, on a call) and you have verified the encryption end to end, past every piece of our infrastructure.
What we deliberately don't do
- No subresource-integrity theater. SRI protects you when your HTML and your scripts come from different parties. Ours come from the same origin — if that origin were malicious, it would alter the HTML and the integrity hashes together. Advertising SRI here would be security decoration, not security. The real fix for attack #1 is build transparency, which is on the roadmap below — as a roadmap, not a claim.
- No "military-grade" language, no "unhackable." The strongest claim we make is one we can defend: open, well-studied primitives (X3DH + Double Ratchet), one shared implementation, verifiable delivery hardening, and an honest tier split.
The two-tier trust model
This is the part most browser messengers won't say out loud. The browser and the app run the same cryptography, but they are not the same trust level, and picking between them should be a threat-model decision:
Browser — the low-friction tier
For starting in seconds: no phone number, no email, no account, no install — on a work laptop, a library PC, an iPhone. Strong against network snooping, server data breaches, and mass surveillance of message content: the mailbox only ever holds ciphertext.
Trust assumption you accept: the code is re-served each visit, so a compromised or compelled operator could target a future session.
Installed app — the high-assurance tier
The Android app is a signed binary with the crypto core compiled in. It does not re-download its code from us each time it runs. The direct download page publishes the APK's SHA-256 and the signing certificate fingerprint, so you can verify what you installed — with or without Google Play in the loop.
Who should use it: anyone whose threat model includes the operator itself being compromised or compelled. That's not a marketing dodge; it's the actual answer.
Known limitations, in writing
- Re-served code (attack #1) is reduced, not eliminated. CSP and HTTPS/HSTS stop third parties from injecting script; they do not protect against the origin itself. Fully closing this would need reproducible builds an outsider can rebuild from source — which a private codebase can't offer. A signed build manifest (roadmap) narrows it to "the server served the release we published"; for the strongest guarantee, use the installed app.
- Browser extensions with page access can read your messages. True of every web app ever built. Use a clean profile — or the installed app.
- The CSP still allows inline styles (
style-src 'unsafe-inline', a UI-framework constraint). CSS injection is a far weaker vector than script and script is strictly locked down, but we list it because it's true, and we intend to remove it. - Not yet independently audited. An external audit is planned and is a hard gate before we push Sendant into privacy communities. Until then we say "built on X3DH and the Double Ratchet," never "audited."
- Source code is private and stays private. Sendant is not open source and we do not say it is, and we never tell you to "verify the code yourself." You check Sendant by its behavior and its signed builds — the CSP/HSTS headers on every response, the WASM crypto shared with the native app, and the direct APK's published SHA-256 and signing-certificate fingerprint — not by reading source.
Roadmap: shrinking the trust gapPlanned
- Signed web builds — a signed, published manifest of the web client, so the browser can check that what the server served matches the release we published (integrity, without opening the source).
- Independent audit — of the protocol implementation and the clients by reviewers under NDA; results published whatever they say.
- Tightened CSP — removing
'unsafe-inline'from styles.
Each of these moves an item from "trust us" to "check us" — without publishing source. The order of operations is deliberate: make the served build checkable against a signed manifest, get the CSP as tight as it goes, then have someone qualified try to break it.
Judge it yourself
Open the web app and inspect the headers, the storage, and the network tab — or skip straight to the high-assurance tier.
Open web app Get the verifiable APK Security overview