feat: harden bridge origin/CSP, cache decryption, and overhaul the web client UI

This commit is contained in:
randogoth 2026-09-26 19:04:36 +03:00
parent f637d5be57
commit 80133da100
10 changed files with 975 additions and 352 deletions

View file

@ -10,7 +10,9 @@ The protocol is raw TCP with a Noise_NX handshake, and browsers cannot open TCP
browser (all crypto, all keys) ⇄ bridge.py (dumb byte pipe) ⇄ smolmaild (TCP :1961)
```
`bridge.py` serves the web app and relays WebSocket bytes to the server untouched. It holds no keys, never parses a byte of traffic, and relays only to allowlisted ports (`--allow-port`, default 1961), so it is not an open proxy. The Noise session, sealing, signing, pinning and trust-on-first-use all run in the browser's JavaScript — the trust boundary is the machine running the browser, as with the CLI client's `identity.key` file.
`bridge.py` serves the web app and relays WebSocket bytes to the server untouched. It holds no keys, never parses a byte of traffic, and relays only to allowlisted ports (`--allow-port`, default 1961), so it is not an open proxy. It also relays only for upgrades whose `Origin` is its own page — WebSockets are exempt from CORS, so without that check any page the user happened to visit could drive the relay. `--allow-origin` admits others, and an upgrade carrying no `Origin` at all, which a browser never sends, is left alone for non-browser clients. The Noise session, sealing, signing, pinning and trust-on-first-use all run in the browser's JavaScript — the trust boundary is the machine running the browser, as with the CLI client's `identity.key` file.
The bridge exists only because the protocol is TCP, and it is the smaller dependency it looks like: `devbox run test` already needs Python and `uv` for the reference vectors and the end-to-end run. What would retire it altogether is a WebSocket transport binding in `smolmaild` itself — Noise messages already carry a `u16` length prefix, so a binary WebSocket stream is a drop-in byte stream — but that is an addition to SPEC.md §4 and to the server, not something this client can decide.
## Run
@ -58,6 +60,6 @@ What is not verified here: clicking through the actual browser UI. `devbox run s
## Notes and limits
- The seed sits in localStorage: an XSS in the page or a compromised browser profile is game over, same as a stolen `identity.key` for the CLI client. No server push or notifications in v1 (SPEC.md §13); click fetch.
- The seed sits in localStorage: an XSS in the page or a compromised browser profile is game over, same as a stolen `identity.key` for the CLI client. The bridge serves a strict Content-Security-Policy (`default-src 'self'`, no inline script or style, no foreign origin) to raise that bar, but it is a mitigation and not a fix. No server push or notifications in v1 (SPEC.md §13); click fetch.
- Rotation is not revocation (§7): a stolen key can rotate onward and the chain validates. The UI surfaces rotations instead of applying them invisibly; out-of-band re-verification is the only defence.
- The bridge binds to 127.0.0.1. Serving it beyond localhost is possible but unadvised: the relay is plaintext-agnostic (the traffic is already end-to-end encrypted, but the bridge would relay anyone's bytes to allowlisted ports).