Please file a private report through GitHub's security advisories at github.com/NMichael111/life/security/advisories/new. Do not open a public issue for a suspected vulnerability.
I aim to acknowledge new reports within 7 days and to provide a fix or mitigation plan within 30 days for confirmed issues.
The server must never see plaintext user data or a password.
Every design decision is checked against this. If a feature would require the server to read plaintext, it is implemented client side or it is not implemented at all. A few examples:
- Weather lookups go directly from the browser to Open-Meteo. The coarse coordinates (rounded to about 11 km) live encrypted in the vault.
- Image drag-and-drop from another tab uses a client-side
<img crossOrigin="anonymous">plus canvas plustoBlobpipeline. There is no server-side image proxy. - Vault export and import are plaintext JSON files the user saves to their own disk. The server is not involved.
- Passive network observers. TLS 1.2 and 1.3 only, HSTS with preload, no mixed content.
- Server-side attackers with read access to CouchDB and config. They see ciphertext, OPAQUE registration records, the OPRF seed, and the JWT secret. They can attempt offline brute force against a weak password. A strong password remains computationally infeasible to crack. OPAQUE's design means the stored record is not a password hash, so the cost of cracking is the cost of guessing the password itself.
- Remote online attackers brute-forcing logins. Per-IP and per-(IP, username) rate limits, asymmetric wall-clock padding (about 600 ms on success and about 3 seconds on failure), OPAQUE's own computational cost, and an enrollment path that is gated by an out-of-band invite token.
- Username enumeration via timing or response content.
RegistrationRecord.createFake()supplies a deterministic fake record per lowercased username, and a fixed response-time floor makes "unknown user" and "wrong password for real user" indistinguishable. - Replay of invite tokens. Tokens are single-use with a 24 hour TTL. The on-disk filename is the SHA-256 of the token, so a directory listing cannot be replayed into a valid enrollment. Consumption uses POSIX rename as the atomic claim primitive.
- An active server compromise that ships modified JavaScript to the user's browser. This is the well-known Web Crypto Trust Problem. The mitigation plan is to host the static frontend on a third-party CDN backed by the public source repo, so users trust a well-known CDN to faithfully serve the repo rather than trusting the operator to do it. Until then, the trust anchor is the operator's reputation.
- An attacker who has the user's password. By design. The vault key
is derived from the password through OPAQUE's
export_key. There is no recovery path. - A malicious user with a valid JWT. They can only reach their own vault database, enforced by the couch-proxy allowlist, and even then the blob is encrypted with their own key.
These are enforced in code and should not be relaxed without careful review.
- No plaintext user data on the server, ever. This includes derived data.
- The password never leaves the browser. OPAQUE enforces this for login. Enrollment enforces it because registration runs entirely on the user's device.
- The CSP must not include
'unsafe-inline'or'unsafe-eval'. Inline scripts are whitelisted by SHA-256 hash only. - The couch proxy allowlist is the single gatekeeper for writes.
The proxy authenticates to CouchDB as admin, so CouchDB's
validate_doc_updateis skipped. Three URL shapes, three methods (GET,HEAD,PUT). Nothing else is allowed. - Rate limiters are keyed on the socket peer IP, not on
user-controlled headers. The edge proxy overwrites
X-Forwarded-Forwith the real peer. - JWTs carry a
verclaim. BumpingtokenVersionon a user document invalidates every outstanding session for that user within the cache TTL.
The server has had an internal review pass and addressed every finding. External review has not yet been solicited. Reports of concrete issues, including disagreement with the threat model, are welcome.