# The web client problem: checking the code that decrypts

> A browser vault runs code the server sends on every visit. What a tampered page could see, which checks Crunu builds in, and what you can verify today.

URL: https://crunu.com/blog/checking-the-code-that-decrypts
Language: en
Updated: 2026-10-30

Any vault that decrypts in the browser has the same weak point: the code that does the decrypting is sent by the server, fresh, every time you open the page. If that server is compromised, it can send a page that looks the same and quietly keeps what you type. In Crunu, such a page would see your password and Secret Key when you sign in, which is why the product names a tampered web client as its main remaining risk. The defenses are a strict content security policy with integrity checksums, signed releases, a reproducible build, and a single-file client, `crunu.html`, that runs from your disk and checks the site against the signed release. Some of these you can check yourself today. The ones that make the check independent of the server, a downloadable `crunu.html` and a release key published through a separate channel, aren't published yet.

This post explains the problem, what each defense covers, and exactly where things stand. The guide on [who can decrypt your secrets](https://crunu.com/guides/who-can-decrypt-your-secrets) puts this risk next to the others.

## Why the web is different

A desktop or mobile app is installed once. Its updates arrive through a store or an updater, and they're signed by a key that isn't on the server you talk to every day. A web app has no install step. Meta's engineering team put it plainly when they built a verifier for WhatsApp Web: "a web app is usually served directly to users, without a third party reviewing and auditing the code" ([Engineering at Meta: Code Verify](https://engineering.fb.com/2022/03/10/security/code-verify/)).

For a vault, that matters more than for most sites. [End-to-end encryption](https://crunu.com/learn/end-to-end-encryption) moves trust from the server to the code on your device. In a browser, the code on your device came from the server a second ago. A server that can't read your secrets in storage could, if compromised, send code that reads them on arrival.

## What a tampered page could and couldn't do

Crunu's server stores ciphertext, public keys, signed documents and metadata such as names and email addresses, and nothing it holds opens a value. Your account key is derived on your device from your password and your Secret Key, and neither is sent over the network.

*Figure: A diagram of Crunu’s keys in six levels, with your device on the left and the server on the right. Your password and Secret Key stay on your device and are never sent. Together they make your account key, which never leaves the device either. It decrypts your own X25519 and Ed25519 keys, which the server holds only encrypted. Owners and admins also hold the organization’s signing and recovery keys, sealed to them. The organization’s signing key certifies who may receive each environment key. Every environment has its own key, and the server holds only sealed copies of it. Each version is encrypted with AES-256-GCM, padded to 4 KB and signed by its author, and the server holds only that ciphertext.*

A modified page changes the "on your device" part. Running in your browser, it would see your password and Secret Key as you type them, and anything you decrypt while it's open. It could send them somewhere.

What it couldn't do, even then:

- **Sign as someone it didn't capture.** Access rules and versions are signed by the people who make them. A page that captured one person's keys could act as that person, but not as anyone else, and its changes would be signed and recorded under that person's name.
- **Reach the agent's machines directly.** Agents on servers and CI check every signature they receive and refuse anything that doesn't verify. They don't run code from the web page.
- **Undo what other devices have seen.** Clients remember the newest state they've seen and refuse older ones.

That doesn't make the risk small. Capturing an admin's keys is close to the worst case. It does mean the risk sits in one place, the page you sign in on, and the defenses can concentrate there.

## Layer 1: the browser refuses code from anywhere else

Crunu's pages send a content security policy that allows scripts only from the page's own origin. You can see it yourself:

```sh
curl -sI https://app.crunu.com | grep -i content-security-policy
```

On October 30, 2026, that printed a policy starting `default-src 'none'; script-src 'self'`, which blocks third-party and inline scripts. The page also loads its own code with integrity checksums. Subresource Integrity, in MDN's words, lets "browsers verify that resources they fetch … are delivered without unexpected manipulation": if a file doesn't match its hash, the browser refuses to load it ([MDN: Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)).

What this stops: injected scripts from a third party, a tampered file on a cache or CDN, and an attacker who can change one file but not the page that names it. What it doesn't stop: a server that changes the page itself. The policy and the checksums are in the response from that server, so whoever controls it controls them too.

## Layer 2: a signed list of what the release contains

Each web release comes with a manifest, `release.json`, listing every file of the site and of `crunu.html` with its SHA-256, and a signature, `release.sig`, made with the release signing key. That key isn't on any server; deploy scripts carry the signed files, not the key.

You can check that the cloud serves files that match its manifest:

```sh
curl -s https://app.crunu.com/release.json | grep -o '"name":"app.js","sha256":"[0-9a-f]*"'
curl -s https://app.crunu.com/app.js | shasum -a 256
```

On October 30, 2026, the two hashes matched. Be precise about what that proves: the server's files agree with the server's manifest. A compromised server could change both. The check that matters is whether the manifest's signature verifies against the real release key, and that key must come from somewhere other than the server. Today the page carries the key in a `<meta>` tag, which the same server sends. The release key isn't yet published through a separate channel, so an outside user can't yet make this check independent of the server.

*Figure: The Crunu sidebar footer while the web app loads: build 4f0c2b9e71d3 first shows “Verifying build…”, then a green “Signed build”. Clicking the badge opens the check: the page is a signed build of version 6.0.0, and the site files match the signature by SHA-256.*

## Layer 3: a build anyone can repeat

The web build is reproducible: the same commit produces the same bytes. That means a release can be checked without trusting the signer at all, by rebuilding it from source and comparing hashes with the manifest. Reproducible builds turn "trust the key" into "trust the source code you read". The glossary entries on [reproducible builds](https://crunu.com/learn/reproducible-build) and [code signing](https://crunu.com/learn/code-signing) explain both ideas.

The honest status: the source code isn't public, so today only people with access to it can do this.

## Layer 4: a client that comes from your disk

`crunu.html` is the same web client as a single file. Opened from your disk, its code doesn't come from the server at all; it talks to the server only for data. On start, it fetches the site's `release.json` and `release.sig`, verifies the signature with the key built into the file, and compares the site's files with itself. The product recommends that administrators work from this file, because their keys are the most valuable.

This is the layer that changes the trust model. With the file, a compromised server can't replace the code you run; it can only refuse to serve you or serve data your client will reject.

**Where it stands today:** the file is built and signed with every release, and it's listed in the cloud's manifest. A public download of `crunu.html`, with its release key published separately, isn't available yet. Until it is, everyone, administrators included, uses the web app as served.

## Things that aren't about the server

Two risks sit next to this one and no web defense helps with them:

- **Browser extensions** with access to all sites can read any page, including a vault's. Use a browser profile without extensions for vault work.
- **Malware on the device.** If the computer where you decrypt is compromised, a keylogger or a root process sees what you see. Crunu's limitations say so directly: there's no protection against malicious code on a device where keys are decrypted.

## What you can do now

- **Check the headers and the manifest** with the commands above, and notice if they ever change in ways you didn't expect.
- **Use the command line for routine work.** `crunu` is a binary installed once; `crunu self-update` checks the release manifest's signature with a key built into the binary and the SHA-256 of the file. Day-to-day use doesn't depend on code the server sends.
- **Keep admin sessions short** and on a clean browser profile. Fewer people with owner or admin roles means fewer keys worth capturing.
- **Watch the audit log and alerts.** A sign-in from a new device raises an alert, and every change is signed by its author.

The [security limitations page](https://crunu.com/security/limitations) lists this risk with the others, and [what "the server can't read it" actually means](https://crunu.com/blog/what-the-server-cant-read-actually-means) covers what the server can and can't do to stored data.

## Sources

- [Code Verify: an open source browser extension for verifying code authenticity on the web, Engineering at Meta](https://engineering.fb.com/2022/03/10/security/code-verify/), checked October 30, 2026.
- [Subresource Integrity, MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity), checked October 30, 2026.
