Client-side encryption sounds like a silver bullet for secret sharing. It isn't — it's a specific trade-off that protects against a specific threat, and it charges you real capabilities in exchange. This guide walks through what browser-side encryption actually guarantees, where it quietly breaks down, how it compares to the server-side model LinkPilot uses, and how to choose between the two without marketing fog.
What client-side encryption actually does
In a client-side encrypted (often called end-to-end encrypted, or E2EE) secret tool, the workflow looks like this:
- The browser generates a symmetric key — typically a 256-bit
AES-GCM key via the Web Crypto API (
crypto.subtle.generateKey). - The payload is encrypted in the browser with that key.
- Only the ciphertext is sent to the server, which stores it under a random slug.
- The key is appended to the URL as a fragment (
#key=…). Per RFC 3986, browsers never send the fragment portion of a URL in HTTP requests, so the key does not cross the wire to the server. - The recipient's browser reads the fragment, fetches the ciphertext, and decrypts locally.
The server only ever sees ciphertext. Even a malicious server operator can't read the secret — provided every other link in the chain holds.
Anatomy of an E2EE secret URL
https://example.com/s/x7Kd93hF2a#key=4fT9wLm2...Qz1
└──────── sent to the server ───┘└── browser-only ──┘
Everything before the # travels in the HTTP request and can appear
in server logs. The fragment is processed only by the recipient's
browser. That one character is doing all of the security work —
which is elegant, and also the root of most of the failure modes
below.
What it doesn't do
- It doesn't protect against malicious JavaScript. The encryption code runs in the browser, and the browser got that code from the server. If the server delivers compromised JS — because the operator turned malicious, was compelled, or was hacked — the secret can be exfiltrated before or after encryption. This is the fundamental limit of browser-based E2EE.
- It doesn't protect against browser extensions. Extensions with page access see the plaintext after decryption, and can read the URL fragment directly.
- It doesn't make URL leakage harmless. The fragment is part of the URL. If the recipient pastes the full URL into chat, forwards the email, or has browser history sync enabled, the key travels with it. An E2EE link with a leaked URL is a fully readable secret.
- It doesn't audit, scan, or preview. Because the server can't read the payload, it can't help with content filtering, malware scanning, data-loss prevention, or meaningful admin visibility.
The code-delivery problem, spelled out
The subtle point worth sitting with: browser E2EE means trusting the same server you supposedly don't trust to deliver honest JavaScript on every single page load. A native app is installed once, signed, and versioned; a web page is re-fetched every visit. Subresource integrity and published source code narrow the gap but don't close it.
So the honest reading of "we can't read your secrets" is: "we can't read your secrets passively." Turning a passive capability into an active attack that ships modified code is still a meaningful upgrade — active attacks are riskier, targeted, and potentially detectable — but it is not the same as "impossible," and vendors who imply otherwise are overselling.
Server-side encryption with hashed passphrases
The pattern LinkPilot uses, documented in detail on the Security Architecture page:
- TLS in transit for all traffic between the recipient, the edge, and the database.
- Encryption at rest — payloads live in a dedicated
secret_linkstable, logically separated from analytics data, on a managed Postgres instance with disk-level encryption. - Passphrases hashed client-side with SHA-256 before transport, then compared by hash on the server. The raw passphrase never reaches the database or logs.
- Destructive burn — when a secret is burned, expires, or is
revoked, the stored payload is overwritten with
[REDACTED]and later reveal attempts get HTTP 410. There is no restore. - Brute-force resistance — passphrase attempts are rate-limited to 5 per IP per minute per secret, and every failed attempt lands in the audit log with a hashed IP.
The server can read an active secret's payload — that's what lets it enforce atomic burn-after-read, feed the audit timeline, and serve a clean reveal flow. It's also exactly the property E2EE removes. Neither is free.
Trade-off summary
| Concern | Client-side E2EE | Server-side + hashed passphrase |
|---|---|---|
| Malicious server operator (passive) | Mitigated | Trust required |
| Malicious server operator (serves bad JS) | Not mitigated | Not mitigated |
| Compromised JS / supply chain | Not mitigated | Not mitigated |
| External attacker with a DB dump | Mitigated (ciphertext only) | Mitigated (encryption at rest; burned payloads already redacted) |
| Leaked URL, no passphrase | Secret fully readable — key is in the fragment | Secret readable until burn/expiry; passphrase closes this |
| Server-side audit log | Access events only | Full lifecycle: created, viewed, failed passphrase, burned, expired, revoked |
| Recipient UX | Slightly worse | Native |
| Admin / team visibility | None | Configurable |
Neither model is "more secure" in the abstract. They defend different threats with different costs.
Matching the model to the threat: three scenarios
1. Sending a contractor a database password. Threat: the secret lingering in email or chat, or a stray forward. Both models handle this well; the deciding factors are usability, expiry controls, and the audit trail. Server-side with a short expiry, burn-after-read, and an out-of-band passphrase is the pragmatic pick.
2. A journalist receiving material from a source. Threat model includes the platform operator being compelled to disclose. Here the operator is part of the threat model, so E2EE (or better, purpose- built tools like SecureDrop) is the right call, accepting the usability cost.
3. An engineering team doing routine credential hand-offs. Threat: sprawl, forgotten links, no record of who saw what. The features that matter — audit timelines, revocation, team workspaces, expiry policy — all live server-side. E2EE would trade away the controls this team actually needs.
How to choose
Pick client-side E2EE when:
- You don't trust the server operator — including yourself running on cloud infrastructure you can't fully verify.
- Regulatory or contractual rules require that the provider be technically unable to read the content.
- The trade-off of zero server-side features is acceptable.
Pick server-side encryption with hashed passphrases when:
- You need team workspaces, audit logs, or admin visibility.
- You want the server to defend against link unfurlers, passphrase brute-force, and abuse.
- Your threat model is "external attacker" and "secret left behind in chat," not "compromised operator."
When not to use either
Some secrets shouldn't travel through any sharing link. Long-lived root credentials, production signing keys, and bulk data exports belong in a secrets manager or KMS with access control, not in a URL of any kind. And if the recipient needs the value repeatedly, a password manager vault item beats re-sending links — one-time delivery is for hand-offs, not storage.
Honest positioning
We say this on the Security Architecture page too: LinkPilot does not currently claim end-to-end encryption, and we'd rather state that plainly than ship a misleading label. Don't pick a tool based on which one claims more. Pick based on what you're actually defending against — and verify that the claims on the tin match the architecture underneath.