By Freddy ·
“It's encrypted client-side, so the server never sees your data” is a true and useful claim, and also an incomplete one. Client-side encryption genuinely removes a whole category of risk: a network operator sniffing traffic, a database backup left readable after a breach, an engineer with admin access to production who has no business reading your plaintext. What it does not remove is the question of whether you can trust the code that ran the encryption in the first place. That question does not go away just because the math happened in a browser tab instead of on a server.
The real, defensible win is moving the point of encryption earlier in the data's journey. If a password manager encrypts your vault with AES-256-GCM in your browser before anything is sent over the network, then a server breach, a misconfigured storage bucket, or a subpoena served to the hosting provider all hand over ciphertext, not your passwords. That is a meaningful security property. It is also exactly the property client-side encryption is designed to provide, and it holds up well against every threat model built around someone downstream of your browser: the network, the server, the database, the backup tape.
Every time you load the page, your browser downloads JavaScript from a server and runs it. That JavaScript is what performs the encryption, generates or imports the key, and decides what gets sent where. If that code is compromised, whether through a hacked deploy pipeline, a malicious dependency pulled in at build time, or a targeted edit served only to your session, the encryption itself can be completely correct and still not protect you: the compromised code can simply exfiltrate the plaintext or the key before encrypting anything, or send a second, unencrypted copy somewhere else entirely. “Runs in the browser” describes where the computation happens, not who controls what that computation actually does. The server that serves the page is still the party you are trusting, on every single page load, not just once.
This is different from a threat you can fix with better cryptography. No choice of key size or cipher mode helps if the code doing the encrypting was never honest about what it encrypts. A correctly implemented AES-256-GCM call inside malicious JavaScript is not a security control, it is theater. The trust model question is upstream of the crypto, not downstream of it.
A cross-site scripting bug on the same origin as a client-side encryption tool is a full compromise of that tool, since injected script runs with the same access to the page's keys and plaintext as the legitimate code does. A compromised CDN or a hijacked npm package pulled into the build is another route to the same outcome, and it does not require attacking your server at all, only something in the chain that produces the JavaScript your browser eventually runs. A malicious browser extension with broad page permissions can read the DOM, intercept a key before it is used, or capture plaintext straight out of a form field, entirely outside whatever the page's own code does correctly. None of these are exotic; they are the standard threat list for any web application, and client-side encryption does not opt out of it.
Take the AES tool on this site at face value as an example of what the trust model actually is. The Encrypt / Decrypt tool runs encryption in a Web Crypto worker, and nothing is sent to a server while you use it, which is a true and verifiable claim (open your browser's network tab and watch it for yourself). But that claim is only as strong as the page you loaded it from. You are trusting that this domain wasn't compromised today, that the JavaScript served to you matches what a security-conscious reviewer would expect, and that no malicious browser extension is sitting between the page and your keystrokes. A tool like this is genuinely useful for a quick, ad hoc encryption need or for sanity-checking an implementation, exactly because that trust decision is small and short-lived: you make it once, for one operation, and you can close the tab afterward. It is a different, much larger trust decision to build a production system where users rely on a specific origin's JavaScript being honest every day for months or years.
There is no way to fully eliminate the need to trust the code you run, but real systems narrow it. Subresource Integrity pins the hash of a script so a compromised CDN can't silently swap it, though it doesn't help if the origin serving the page is what's compromised. A strict Content Security Policy limits what a successful XSS injection can actually do, cutting off the easiest exfiltration paths even if a bug slips through. Reproducible, open-source builds let outside parties verify that the JavaScript shipped to users matches audited source code, rather than asking for blind trust in a vendor's word. None of these make the trust boundary disappear; they make it smaller, more auditable, and harder to compromise silently, which is a genuinely different and better position than assuming the phrase “client-side encryption” already settled the question.
Client-side encryption is not a marketing claim to be suspicious of, it is a real architectural choice with a real, specific threat model: it protects data in transit and at rest from everyone who isn't the code running in your browser at the moment you use it. That is a large and valuable set of attackers to remove from the picture. It just is not the same as removing trust from the system entirely, and treating it that way is where the real risk hides, not in the AES implementation itself.