Sendant
Security · the hard question first

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: blob:; media-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

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.

If the stakes are life-and-safety: use the installed app, verify the safety number with your contact out-of-band, and treat any browser session as the convenience tier. We would rather lose a user to our own Android app than overstate what a browser can promise.

Known limitations, in writing

Roadmap: shrinking the trust gapPlanned

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.

Corrections welcome. This page is meant to survive expert scrutiny. If any claim on it is wrong or has drifted from what ships, email privacy@sendant.io with "SECURITY" in the subject — we'll fix the page or the product, whichever is at fault.

Common questions

Is end-to-end encryption in a browser as safe as an installed app?

No — and we say so on our own security page. A browser re-fetches its code from the server on every visit, so you re-trust the operator each time; an installed app is a signed artifact that does not change under you. We treat the browser as the low-friction tier and the installed app as the high-assurance tier, and we recommend installing to anyone whose threat model includes a compelled or compromised operator.

Can the Sendant server read messages sent from the browser?

Not as the system is built: messages are end-to-end encrypted on your device by a WebAssembly build of the same crypto core the native apps use, and the mailbox only ever stores ciphertext. The honest caveat is the one this page opens with — a malicious operator could in principle serve altered JavaScript to a future browser session and capture messages from it. That residual risk is inherent to every web app, which is why we publish a two-tier trust model instead of pretending it away.

What stops a hacked or malicious server from serving altered JavaScript to Sendant web users?

Today: a strict Content-Security-Policy (scripts only from the app's own origin, no third-party scripts at all), HSTS with preload, and a single crypto core — the same one the native apps run, compiled to WebAssembly for the browser. What it does not stop is the operator itself turning malicious, which is exactly why we recommend the installed, signed app for high-assurance use, and why signed-build transparency for the web client is on the roadmap rather than claimed today.

Which is more secure: Sendant in the browser or the installed app?

The installed app. It is a signed binary that ships its cryptographic core compiled in and does not re-download its code from us each time it runs — and on Android you can verify it yourself against the SHA-256 and signing-certificate fingerprint published on the download page. The browser client runs the same cryptography but inherits the web's delivery model. Use the browser to start in seconds with nothing installed; move to the app when your threat model demands it.

Related reading

How the browser tier holds up in practice: what a browser-based messenger can and cannot promise for privacy, verifying a messenger without source code, and protecting sensitive files without leaking metadata.

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