I've actually been thinking quite a bit about this very issue. As it stands, it's not really possible to do E2E encryption on the web in a secure way, since the server can always just silently update the client side code for a particular user to steal any encrypted data. I'm kind of curious about what you're doing with service workers to lock the server out of being able to update its own client side application. That sounds almost like a bug.
My ideal solution to this problem would be Web Bundles[1] signed by the server's TLS key[2], combined with Binary Transparency[3] to make targeted attacks impossible to hide (and maybe independent Static Analysis[4] to make attacks impossible to carry out in the first place), but work on many of those standards seems to have died out in the last few years.
I've looked at web bundles and a variety of other solutions myself, but the service worker approach feels like a winner so far. There's no magic, nor any bug being abused, but the client does have to trust the server to behave nicely during initial setup. After the initial setup is done, the client never again has to trust the server again as long as the browser's local storage isn't purged manually; so if the server is compromised after the initial setup, the compromised server cannot compromise established clients. It's not perfect, there's still the need for initial point-in-time trust, but it's still a significant improvement on the standard way of serving webapps where a server can compromise any client at any time.
The way it works is the server returns a unique service worker script every time, and the script file itself contains an AES key. The user trusts the server not to store this key and the server never sees it again. This AES key is then used to encrypt all persisted local state and sign all cached source files. If the server replaces the service worker, the key is lost and local state cannot be accessed. If the server somehow replaces a source file, its integrity check will fail and the webapp will refuse to load it. If the server manages to skip the service worker and serve a malicious file directly (e.g. because the user did Shift+F5), the malicious file won't have access to any local state because the service worker will refuse to give it access. The server can destroy all local state and then serve a malicious application, but the user will immediately notice, hopefully before interacting with the app, because suddenly all their data is gone.
That's really clever! Fixes the "silently" part at least, though given that most applications typically require frequent updates and that this doesn't prevent targeted attacks, I'm not sure how useful it is in practice, at least for mainstream applications.
Signed web bundles with binary transparency and independent review would be far superior, if they actually existed. (Which sadly, they don't right now.)
Thanks! Automatic updates are still possible; you can implement a code signing-based flow on top of this, or fetch hashes from GitHub releases, or anything, really. Attacks are only possible during setup, and targeting at that point in time is difficult because the client won't have authenticated yet. Anything else (attacks that rely on clearing the local state) can be mitigated using careful UI design.
The big problem with transparency logs is that they can't prevent attacks in real time because of the merge delay. You'll only find out afterwards if you've been attacked. It significantly raises the bar for an attack, but can't stop one from happening.
My ideal solution to this problem would be Web Bundles[1] signed by the server's TLS key[2], combined with Binary Transparency[3] to make targeted attacks impossible to hide (and maybe independent Static Analysis[4] to make attacks impossible to carry out in the first place), but work on many of those standards seems to have died out in the last few years.
[1]: https://wpack-wg.github.io/bundled-responses/draft-ietf-wpac...
[2]: https://datatracker.ietf.org/doc/html/draft-yasskin-http-ori...
[3]: https://datatracker.ietf.org/doc/html/draft-yasskin-http-ori...
[4]: https://datatracker.ietf.org/doc/html/draft-yasskin-http-ori...