summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-14 14:43:48 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-14 14:43:48 +0200
commit9fa2117de1caf4d713cc0b7a310b9549467738c3 (patch)
treed1ac0317a714dda175f8441f1954b81a8fe56cb3 /CLAUDE.md
parent1c96aceb54d66cae1b48aa0eb8887f68e53f9e24 (diff)
downloadmeshbay-9fa2117de1caf4d713cc0b7a310b9549467738c3.tar.gz
docs: Argon2id, the multi-browser property, and what a browser found
draft-v5 §7 rewritten around the keypair bundle, because that is where the last open finding actually lives. New §7.1 states the adversary (an operator holding their own node's disk), what cracking a bundle yields (identity keys, hence content on *other* nodes and the ability to sign as that user — not the content they host in the clear by design), and the measured numbers rather than adjectives: PBKDF2 241 ms vs Argon2id 88 ms natively, a GPU ceiling moving from ~8k to ~2k guesses/s, six days for a 10⁹ dictionary run, four random words outlasting the sun. The honest summary is in there too — a factor of four on one card, not a thousand; what it buys is the cost of scale. §2 gains the row the table never had: **your identity keys stay yours**, ⚠️ against a malicious node operator. An operator hosts your content by design, and that was documented; that they can also try to become *you* was not. That is the difference between reading what they host and reading what other operators host. §4 records that the challenge now carries `node_pk`, why (a first-time member signs a transcript naming the node and has no GEK to complete a handshake with), and that it is checked against the ack rather than trusted. Also that refusals carry a code, and what `not_a_member` usually means. §8.1 states the multi-browser property plainly — one identity across browsers, recovered with the passphrase, no second code — together with its cost, since it is the same mechanism as C4. invite-pairing-v1 is no longer "a proposal": it shipped. §9bis gains the four browser-found failures and their common thread — e2e.py is a second implementation of the client, written in the right order by construction, so it proves the protocol and nothing about app.js. CLAUDE.md gets the two things a future session must not rediscover the hard way: the KDF parameters live in three places held identical by a parity test, and an unbounded await on the hub socket makes a node silently unreachable (three found). second-review: C4 marked reduced, not closed. devel-phases-next: 12.2's CSP must keep `wasm-unsafe-eval`, or the strict policy locks every user out of their keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md31
1 files changed, 31 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index b2fb9b0..b8c2646 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -193,6 +193,37 @@ See `docs/invite-pairing-v1.md`. Read it before touching invites, admin authorit
- Revocation now works for key delivery (nothing stored survives it) — but **still rotate
the GEK**, the ex-member holds the current one
+## Keypair bundles and the browser KDF (2026-08-14)
+
+- The bundle key is **Argon2id 128 MB / t=3 / p=1**, WebAssembly vendored under
+ `static/vendor/` (CSP forbids external hosts; 12.2 must keep `wasm-unsafe-eval`).
+ **Do not change the parameters in one place**: `keyderive.js`, the QE harness and
+ `test_bundle_kdf_parity.py` are held byte-identical by that test, and a mismatch
+ presents as an account nobody can open
+- Bundles carry an `MBK2` marker; the PBKDF2 form is still readable and is
+ re-encrypted on the next backup. Both keys are derived at sign-in because the
+ passphrase is deliberately not retained
+- Cost is paid **once per sign-in** (650 ms bundle + 239 ms auth_key); reloading a
+ page derives nothing — the key lives in IndexedDB
+- The bundle is stored on **every node its owner joins**. That is what makes a
+ second browser work, and it is C4: cracking one yields identity keys, hence
+ content on *other* nodes and the ability to sign as that user. Draft-v5 §7.1 has
+ the measured numbers. **The passphrase is the wall; the KDF is a speed bump**
+- Floor: 12 characters and ~60 estimated bits, enforced client-side only — with the
+ password split (T1) the hub never sees a passphrase
+
+## Two lessons that cost four rounds of live testing
+
+- **`QE/deploy/e2e.py` cannot test `app.js`.** It is a second implementation of the
+ client, written in the right order by construction: it proves the protocol and
+ nothing about the SPA. Three ordering bugs passed it and failed in a browser.
+ `test_spa_ordering.py` exists for that class and is worth extending
+- **An unbounded `await` on the hub socket makes a node silently unreachable.**
+ Three instances found in `maintain_ws`: the offer handler awaited inside the read
+ loop, `ws.recv()` for auth with no timeout, and `return` on auth refusal ending
+ the task for good. Symptom is always the same — daemon running, logging nothing,
+ `connected_nodes: 0`, socket in CLOSE-WAIT. Look there first
+
**Corrections to remember:**
- `punch_nat()` is **not** a NAT traversal stack — one UDP probe, no STUN, no candidate
gathering, one ISP validated. **ICE/STUN (WebRTC) is the traversal path**, for native