summaryrefslogtreecommitdiffstats
path: root/docs
Commit message (Collapse)AuthorAgeFilesLines
...
* docs: record the invite redesign — H3 and M3 closedChristophe Besson2026-08-142-70/+129
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | draft-v5 §2: against an active hub, reading content moves from "❌ H3" to "❌ T3 (browser) · ✅ native". The defensible sentence becomes "the hub cannot read your content unless it ships you malicious client code" — T3 is now the only path, it is an artifact rather than a silent directory lie, and it does not exist for a native client. New §5.5 describes admission and key delivery, with the four properties that carry it and the one exception (open-join groups, where the hub can walk in the front door — a property of open joining, and the setting is read from node.toml). Corrected while writing it: §5.1 said the C5b fix stopped a group admin who does not run the node from inviting, and that the redesign reverses this. It does not, because delegation was deferred. What changed is the timing — the operator issues a code and is then out of the loop. devel-phases-next: 12.1 is done and NOT as written. The plan was key transparency plus safety numbers; what shipped removes the directory read instead. Safety numbers make substitution detectable by a human who checks, at first contact, when there is nothing to check against. 12.2 (served-SPA integrity) is now the highest-value item in that phase. Phase 14 marked for what landed. second-review: H3 and M3 annotated closed at the finding, with what actually closed them. The §7 verdict table is left intact — it is the record of an audit on a date, and falsifying it would be worse than leaving it — with a note pointing at draft-v5 §2 for current state. CLAUDE.md matters most here, being loaded every session: NS4 read "admin_pk_ed25519 auto-pinned from keystore ✅ DONE", which is M3 described as a feature. Rewritten, with the two fixes that must never be attempted (auto-pin, hub lookup). QE/deploy/README.md: set-admin-pk retired from the walkthrough; the regression checklist now exercises pairing, joining by code, recognition without a code, and revocation. USERGUIDE.md is beyond the invite work but was actively wrong: it told users to POST GEK bundles to a hub endpoint deleted in Phase 12, and to re-wrap for every remaining member on revocation. Both replaced with what the code does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(node)!: the node wraps the group key — closes H3 and M3Christophe Besson2026-08-141-0/+526
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The invite flow fetched the invitee's pk_x25519 from the hub and wrapped the GEK for whatever came back (app.js:1466, and gek-init did the same server-side). The hub is the key directory, so a hub answering with its own key was handed the group key by an honest member following the protocol exactly. No forgery, no injection, nothing for the client to notice. That was H3. The fix is not safety numbers. Nobody reads the directory any more: - the node holds the GEK and wraps it itself, on every connection, for the X25519 key the joiner signed with their Ed25519 identity in one transcript (meshbay:join:v1), so the identity key vouches for the encryption key; - identities are bound to accounts by a one-time code the hub never sees — 40 bits, single use, one account, bounded per connection AND node-wide; - the node's own roster decides who may receive the key. Hub membership lets someone reach a node; it no longer gets them anything. A hub that invents an account and mints it a token is answered not_authorized_for_group. Safety numbers would have made substitution detectable by a human who checks, at the moment there is nothing to check against — first contact. Removing the lookup makes it impossible, and costs the user one code to pass along. M3 falls out of the same work. The daemon auto-pinned its own keystore key as admin_pk_ed25519 while the browser signs with the user identity key, so every privileged operation failed closed with a signature error that looked like a bug somewhere else; the demo only worked because a deploy script overwrote the value. Authority now comes from the roster, established locally by `operator pair`. Asking the hub for the operator's key — the obvious-looking fix — would have let the hub install itself as node administrator. BREAKING: gek_bundle_store is deleted, not gated. No member hands the node key material at all, so C5b becomes structural rather than an authorization to check. Existing stored bundles are still served, so current deployments keep working. Also: - join_policy (invite|open) is read from node.toml, never from the hub — a hub able to declare a group open would be handed its key. Unknown group ⇒ invite. - admin signatures are verified against the roster on every check, so unpinning takes effect without a restart. admin_pk_ed25519 stays readable as legacy. - two C5b tests were rewritten, deliberately: they asserted that gek_bundle_store demanded an operator signature, and the message is gone. They now assert the stronger property. The file says not to fix these tests, so this is the record of why they changed. - a slice-1 bug found while writing slice 2: connect() never passed skEdB64, so pairing would have failed at runtime with no test able to catch it. Tests: 152 node+common here, including an end-to-end DataChannel run where a member who has never held the group key redeems a code in the pre-proof window and receives the key wrapped for a key only they can open. Design: docs/invite-pairing-v1.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(node): CLI for headless operators — status, ui, gek-initChristophe Besson2026-08-131-1/+17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Every operator action lived behind a web UI on the node's own loopback interface. For the normal deployment — a node on a server reached over SSH — that is unusable: no browser on the host, and 11.5.3 added a per-run token that had to be copied out of a log to get in. status hub, node public key, daemon state, groups, admin-key pinning. Reads the keystore directly so it works while the daemon is STOPPED, which is exactly when it is needed: the daemon cannot stay up before its key is linked or before a group exists. ui prints the URL and the ssh -L line. It does not open a browser — that was an assumption about the environment, and a wrong one. gek-init initialises a group key through the daemon's loopback API. Same operation as the admin UI button, no browser involved. Also fixes a latent bug in QE/deploy/deploy-node.sh: the pkill pattern was unanchored, so it matched any shell whose command line merely mentioned the daemon — including the one running the script. It killed a session three times before being pinned down. Anchored to the end of the command line. Verified against the live deployment. grenet and cbesson both connect over WebRTC through real NAT and can browse, download, stream, upload and chat. The node audit log confirms the security properties in production: uploads land in .uploads/{user_id}/ (C5a), the invite required the operator's signature over an admin transcript (C5b, H5), the pre-proof bundle window is bounded and audited (C4), and a non-member handshake was refused. Docs updated: Phase 14 marked partially delivered with the reason, draft-v5 §5.3 records the two operator personas, QE/deploy/README.md documents the commands and the remaining browser-only gaps (invite, delete). Tests: 121 node. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: draft-v5 — mark C6, M8 and 11.5.8 closedChristophe Besson2026-08-131-12/+29
| | | | | | | | | | | | | | | | | | | | | | | | | | Phase 11.5 is complete. All six critical and all seven high findings from the second review are now closed, bounded, or deferred by explicit decision. Updated in place rather than appended, so the document does not carry stale "open" markers next to shipped work: - §9 split into "closed since this document was drafted" and "still open", with C6, 11.5.6, 11.5.8 and M8 moved across and the closing mechanism recorded for each - §2 claim table: node impersonation is no longer pending - §3.1 QUIC now shows the unified handshake enforced - §4.2 records what the QUIC binding actually turned out to be, including the finding that a resumed TLS session carries no certificate, so the anchor travels with the session ticket - §4.4 states that the client pins pk_node and refuses a change Added a scope note: with C6 closed, pinning is defence in depth, not the primary control. A substituted node already fails the GEK proof; pinning covers the case where an attacker holds the group key and swaps the node underneath. H3 remains the last unfixed finding, and the document still says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: draft-v5 architecture specChristophe Besson2026-08-131-0/+343
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Supersedes draft-v4, which described a system the code did not implement and made several claims that were simply wrong — "ALL operations require the GEK proof" (true on one of four transports), "Argon2id 256 MB" (hub only), "hub stores no content metadata" (private file hashes were registered with it). Written as a delta over v4: sections not restated are unchanged. Carries an explicit rule — a claim must name the adversary it holds against — and a per-adversary table replacing v4's informal assurances. Records the decisions: transport (aiortc primary, QUIC retained, TCP and the node HTTP API removed), unified handshake with mutual authentication, admin operation transcripts, node authority over GEK storage and activation, upload confinement, hub node-registration and signaling authorization, and the client architecture — hub keeps serving the web SPA, native client offered alongside, hub minimization deferred. States plainly what is NOT true. The defensible claim is "the hub cannot read your content unless it actively attacks you", not "unreadable by other parties, even the hub": H3 (hub is the key directory and can substitute a key at invite time) is open until Phase 12.1, and T3 (hub serves the SPA) is accepted permanently by decision. Content is also readable by every group member and by the node operator, so "end-to-end" here means client-to-node, never client-to-client. Corrects the v4 NAT traversal account: punch_nat() is a single UDP probe with no STUN, no candidate gathering and no fallback, validated on one ISP. ICE is the traversal path, including for native clients. Open items listed with status, including C6 on the QUIC path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: Phase 12 — P2P crypto material, password split, node Ed25519 authChristophe Besson2026-08-131-15/+206
| | | | | | | | | | | | | | | | Baseline commit capturing in-progress Phase 12 work that was already present in the working tree (uncommitted) before the Phase 11.5 security remediation begins. Committed as-is, without review or modification, so that remediation changes arrive as a separable diff. Contents: BundleStore (P2P GEK + keypair bundles), password split (auth_key / bundle_key), node Ed25519 auth (POST /v1/nodes/auth, node-scoped JWT), GEK-HMAC handshake proof with DTLS channel binding, Ed25519 admin challenge-response, node local admin UI rewrite, browser key persistence. Not authored in this session — captured to establish a baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: Phase 10b — Self-service UI (group create/join, upload, IndexedDB, ↵Christophe Besson2026-08-111-2/+4
| | | | | | | | | | | | | | | | search) Six self-service features for the web SPA: - Group creation UI with GEK auto-generation (AES-256-GCM ECIES) - Member management + invite by username (GEK wrapping for invitee) - Open group self-join flow (POST /v1/groups/{id}/join) - File upload client→node (FILE_UPLOAD MNP type, .uploads/ staging) - IndexedDB caching of group file indexes (instant display on revisit) - Cross-group file search (SearchPage, pure client-side on cached indexes) 11 new tests (166 total): 8 group self-service + 3 AES GEK wrap/unwrap. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(hub): Phase 10.5–10.8, 10.10 — notifications, settings, search, versionChristophe Besson2026-08-111-2/+7
| | | | | | | | | | | | - 10.5: Notification model + CRUD API (list, mark read, mark all read) Triggered on: group invite, role change, suspend/unsuspend - 10.6: SettingsPage shows role, per-group notification mute (localStorage) - 10.7: GET /v1/groups?q= search filter (ilike on name) - 10.8: NotificationFeed on home page + bell with unread badge in navbar - 10.10: GET /v1/hub/version endpoint for client update checks - 8 new tests (test_notifications.py), 155 total Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update Phase 10 commit hash, API reference, key modulesChristophe Besson2026-08-111-4/+19
| | | | Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Phase 9.1–9.5 — WebRTC DataChannel transport for browser P2PChristophe Besson2026-08-101-0/+1155
| | | | | | | | | | | | | | | | | | | | | | | | Browser clients can now connect P2P to nodes behind residential NAT via WebRTC DataChannel with ICE/STUN. Validated on SFR Port-Restricted Cone NAT + 4G CGNAT across three scenarios (WiFi LAN, 4G IPv6, 4G IPv4 STUN). No TURN relay needed. Hub serves only as signaling relay (<1 KB). New files: - webrtc_server.py: aiortc-based WebRTC transport (node side) - signaling.py: SDP/ICE relay endpoint (hub side) - transport.js: browser WebRTC client with msgpack framing - webrtc-test.html: spike test page for browser→NAT→node validation - test_webrtc_transport.py: 4 tests (handshake, file transfer, auth, guard) - meshbay-draft-v4.md: architecture spec updated for web client Modified: - hub_client.py: WebRTC offer handling via hub WebSocket - revocation.py: node_id from WS auth + webrtc_answer routing - pyproject.toml: aiortc>=1.9 dependency 123 tests passing (117 existing + 6 new). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Phase 7 — Node v2 (multi-group, Sender Keys, 0-RTT, chat, denylist)Christophe Besson2026-08-101-17/+41
| | | | | | | | | | | | | | | | | | | | | | | | | | | Implements all 8 milestones (7.0-7.7): - 7.0: JWT carries `groups` claim; node verifies group membership at MNP handshake (QUIC + TCP+TLS). Resolves security review C2. - 7.1: QUIC 0-RTT session resumption via stored session tickets (17-21ms reconnect vs 47ms cold). - 7.2: Hub→node WebSocket signaling for NAT punch coordination (`client_incoming`/`punch_ready`) + jti denylist push. Denylist class blocks revoked users/jtis at handshake. - 7.3: Multi-group daemon — one QUIC port serves N groups with per-group GEK, shared_root, and index routing. - 7.4: HLS streaming via QUIC (STREAM_SEGMENT message type, ffmpeg segment extraction). - 7.5: Sender Keys protocol for group chat (Signal Groups approach). Each member has own sending chain key, HKDF chain ratchet, AES-256-GCM encryption, Ed25519 signing. Resolves security review C1. - 7.6: Chat store (SQLite via aiosqlite), CHAT_MESSAGE MNP wire type with peer broadcast, web UI with WebSocket push. - 7.7: Argon2id calibration CLI. First security review included (first-review.md). 109 tests, demo-v3 validated against meshbay.org production hub. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update draft v3 + phases-next with Phase 7 decisionsChristophe Besson2026-08-101-11/+43
| | | | | | | | | | Multi-group: single QUIC port (multiplexing), group_id from JWT. Signaling punch/connect: hub WS client_incoming/punch_ready protocol, reduces handshake 12.7s → < 200ms. SFR Port-Restricted findings added. Chat model: between forum and Signal — persistent, threaded, E2E, per-group scope, push for online / pull for offline members. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: 4 corrections — streaming hash, watchdog bug, cipher doc, depsChristophe Besson2026-08-091-1/+12
| | | | | | | | | | | | | | | | | | | 1. indexer.py: streaming blake3 (8MB chunks) instead of read_bytes(). Large files (initrd.img, ISOs, VM images) no longer load into RAM. 2. QE/demo-v1/run_node.py: call indexer.start() not initial_scan(). initial_scan() alone never starts the watchdog observer — files added after startup were silently ignored. Added indexer.stop() on shutdown. 3. USERGUIDE.md §8: clarify symmetric vs asymmetric. Ed25519/X25519 = asymmetric (key pairs). ChaCha20-Poly1305 and AES-256-GCM = symmetric AEAD 256-bit (content encryption). ChaCha20 is PRIMARY; AES-GCM is optional browser-compat variant only. 4. pyproject.toml: aioquic, websockets, aiosqlite, slowapi added to proper package deps (were installed manually, now declared). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: complete pyproject.toml deps + graceful QUIC fallbackChristophe Besson2026-08-091-8/+21
| | | | | | | | | | | | | | | | | | | | meshbay-node/pyproject.toml: add aioquic>=1.0 (was commented 'v2'), websockets>=12.0 (revocation push). Both are production code since Phase 5. meshbay-hub/pyproject.toml: add aiosqlite (tests without PostgreSQL), slowapi (rate limiting), websockets (revocation push), PyJWT (explicit). transport/__init__.py: QUIC imports wrapped in try/except — node works without aioquic (TCP+TLS + HTTP fallback). QUIC_AVAILABLE flag exported. QUICKSTART.md: replace manual pip list with 'pip install -e' that pulls all deps from pyproject.toml automatically. Add dependency table. CLAUDE.md: clarify that all deps go in pyproject.toml, not manual installs. 81/81 tests. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: venv --clear required when copying repo across OS (Fedora→Ubuntu)Christophe Besson2026-08-091-11/+8
| | | | | | | | | | | | | Root cause: certifi.where() in the Fedora venv points to /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem which does not exist on Ubuntu. 'python3 -m venv .venv' without --clear keeps the Fedora certifi paths. Fix: always use --clear when recreating a venv on a different OS. Documented in QUICKSTART.md and CLAUDE.md. rsync command updated to exclude .venv/ (in QE/server-state, not versioned). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix: document pip SSL_CERT_FILE workaround for Python 3.14 on Ubuntu/FedoraChristophe Besson2026-08-091-4/+17
| | | | | | | | pip + Python 3.14 fails with FileNotFoundError in certifi.where() on fresh venvs (truststore bug). Fix: SSL_CERT_FILE pointing to system CA bundle. Documented in CLAUDE.md (Python environment section) and QUICKSTART.md. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* docs: update all pointers after keyderive + QE restructureChristophe Besson2026-08-092-8/+64
| | | | | | | | | | | | | | | | | | | | | | CLAUDE.md: add QE/ to structure, key modules table, server state reference, security rule updated (QE/ not keypair files), meshbay.org inventory pointer. devel-phases.md: add milestones 6.6-6.9 (keyderive, bundle, demo scripts, QUICKSTART rewrite). 81/81 tests. docs/meshbay-draft-v3.md §6.1.1: new section documenting 3 key generation strategies (Argon2id CLI, WebCrypto browser+bundle, keystore file) and the algorithm mismatch caveat between CLI and web registration paths. docs/USERGUIDE.md §2 Register+Login: replace "generate and persist before registering" warning with the two clean strategies (derive_keys_from_password for CLI, keyderive.js + keypair_bundle for browser). Login response updated with keypair_bundle field. hub/models.py + users.py + Alembic migration: keypair_bundle column on User, stored at registration, returned at login (web clients only). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat: password-based key derivation + operational QUICKSTARTChristophe Besson2026-08-091-358/+141
| | | | | | | | | | | | | | | | | | | | | keyderive.py: derive Ed25519+X25519 from username+password via Argon2id. Same credentials → same keys on any device. Encrypt/decrypt keypair bundle (AES-256-GCM) for hub storage (web clients). 7/7 tests. Full suite: 81/81. keyderive.js: browser counterpart using PBKDF2-SHA512 + random keypairs encrypted for hub storage. Avoids algorithm mismatch with Python. hub/models.py + users.py: keypair_bundle field added to User, stored on registration, returned in login response for web client key recovery. QUICKSTART.md: fully rewritten. 3 operational scripts in QE/demo-v1/: setup_demo.py — create accounts, group, distribute GEK run_node.py — start HTTP node (watches shared/ directory) download.py — bob login → GEK fetch → decrypt → save All tested locally end-to-end. No invented URLs. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* docs: add QUICKSTART.md and USERGUIDE.mdChristophe Besson2026-08-092-0/+1219
| | | | | | | | | | | | | QUICKSTART (434 lines): 6-step guide tested against live https://meshbay.org — demo accounts alice_test/bob_test, real transfer of README.txt (23ms) and 1MB chunk (275ms recv, 2.4ms decrypt), exact Python commands with measured output. USERGUIDE (785 lines): 11-section reference — architecture, account management, group/node config, file sharing, HLS streaming, security model, moderation/CSAM, troubleshooting, full API table. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* docs: add HTTPS.md + configure www.meshbay.org redirectChristophe Besson2026-08-091-0/+138
| | | | | | | | | Caddyfile updated: www → 301 → meshbay.org (canonical). Caddy auto-issued Let's Encrypt cert for www.meshbay.org in 4s. HTTPS.md explains the setup, cert lifecycle (90-day, auto-renewed), why 1-year certs are not recommended, DNS requirements. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* docs: add architecture draft v3 with POC findingsChristophe Besson2026-08-091-0/+804
| | | | | | | | | | | | | Key corrections from spikes 1-6: - JWT jti now required (prevents replay, enables revocation) - Argon2id params updated to target 500ms (256MB memory) - NAT order corrected: STUN before UPnP (UPnP unreliable on SFR) - Transport: TCP+TLS v1, QUIC v2 - GEK wrapping protocol confirmed (ECIES-like, 48B opaque bundle) - Hub API table complete with Spike 6 endpoints - 3-package monorepo structure documented Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* chore: initialize monorepo structure for MeshBayChristophe Besson2026-08-096-0/+3183
3-package layout: meshbay-common (shared crypto/protocol), meshbay-hub (FastAPI server), meshbay-node (local daemon). Includes validated POC spikes 1-6 in poc/, architecture drafts v1/v2 in docs/, and CLAUDE.md project conventions. All cryptographic primitives extracted from POC into meshbay_common/crypto.py (GEK wrap/unwrap, chunk key derivation, keystore encryption, chunk signing). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>