| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 11.5.4/5/7/8 — findings C6 (WebRTC half), C3, L4, M1, M9.
New meshbay_common/handshake.py is the single implementation of authorization
and proof: JWT verify, scope, denylist, mandatory group_id, membership, hosting.
The handshake previously existed three times over and only the newest copy
enforced the GEK proof.
C3 — mutual authentication. Authentication ran one way: the client proved
itself, the node proved nothing. handshake_ack.node_pk was never verified
against anything and per-chunk signatures had been dropped in Phase 9.15, so a
peer that had hijacked signaling (C2) or been substituted by the hub could
accept the client's proof, ignore it, and serve a forged index, forged chat
history and a forged is_node_admin flag. The client now sends a nonce; the node
answers with its own GEK proof over that nonce AND an Ed25519 signature over
the transcript; the browser verifies both and refuses otherwise. It also
refuses an unchallenged handshake_ack, which previously let a peer skip proving
anything at all.
L4 — the proof was nonce ‖ offer_fp ‖ answer_fp: bare concatenation, and a
missing fingerprint silently degraded it to nonce-only, dropping MitM detection
(NS5). Every field is now length-prefixed and domain-separated, the role is
bound so a client proof cannot be replayed as a node proof, and an absent
channel binding is refused rather than tolerated.
M1 — group_id was optional; omitting it skipped the membership check entirely
and fell back to the node's first group. Now mandatory.
M9 — node-scoped daemon tokens are refused on the client path.
NOT DONE: quic_server.py still runs its own JWT-only handshake, so C6 remains
open — a forged or stolen token reaches a node over QUIC and can inject chat
without holding the GEK. quic_binding() is written and unit-tested but unwired.
11.5.6 (whether the certificate-hash anchor works with aioquic, or an RFC 5705
exporter is reachable) is unproven. 11.5.8 TOFU pinning of pk_node is not done:
the client verifies the node's signature but does not yet remember which key it
saw last.
Adds packages/meshbay-common/tests/test_handshake.py (18 tests) covering the
properties every transport must inherit. WebRTC test helpers rewritten around
the shared module; _make_jwt now defaults to the test group, since group_id is
mandatory.
Tests: 24 webrtc, 176+ node+common.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 11.5 — findings H1, C5a, H2, C5b, H5 (see second-review.md).
Batched together because the node-side changes share webrtc_server.py and
cannot be separated into working commits.
H1 — cross-group chat leak. chat_store, the peer registry and the display-name
cache were read from the shared transport context, and daemon.py hoisted the
FIRST group's chat store onto it. On a node hosting several groups every
group's messages went to one database, chat_history served them back to members
of every other group, and chat broadcast reached all peers regardless of group.
All three now resolve through _group_ctx().
C5a — upload confinement. Uploads landed in the shared root under a
client-chosen name and overwrote whatever was there. Any member could destroy
the operator's files, and by becoming the recorded uploader of the replaced
file could then delete it through the uploader path, bypassing the Ed25519
admin challenge. Uploads now go to a per-user quarantine (.uploads/{user_id}/),
refuse to overwrite, and enforce chunk ordering, a filename allowlist and a
size cap.
H2 — stored XSS in the node admin UI. Filenames chosen by any group member were
interpolated raw into the localhost UI, which has no authentication, so script
execution there equals control of the node admin API. Now html.escape()
throughout, textContent in the audit table, plus CSP/nosniff/no-referrer. The
CSP contains exfiltration but cannot stop injected inline script — escaping is
the fix.
C5b — group key seizure. gek_bundle_store wrote whatever any member sent and
auto-activated bundles addressed to the node operator. The operator's X25519
public key is public (the node publishes it in handshake_ack), so any member
could wrap a key of their choosing for it and take over the group, locking
every legitimate member out. Storing now requires an operator signature and
_try_activate_gek is removed: nothing arriving over MNP can set a live GEK.
H5 — unbound signing oracle. The node challenged with 32 raw random bytes and
the client signed them blind, so a signature named no operation, subject, node
or time. New meshbay_common/adminop.py defines a length-prefixed,
domain-separated transcript; both sides build it independently and the client
refuses to sign when the announced op/subject do not match its request.
BREAKING: a group admin who does not operate the node can no longer store GEK
bundles on it. Invites must be performed by the node operator.
Adds tests/test_security_regressions.py. Verified against pre-fix source via
git stash. Three pre-existing tests asserted the vulnerable behaviour as a
feature and were inverted: gek auto-activation, and the transport-wide
chat_store in test_daemon.
Tests: 109 node, 132 hub+common.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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>
|
| |
|
|
|
|
|
|
|
| |
Replace download-then-play VideoPlayer with MSE (MediaSource Extensions)
streaming. Node remuxes to fMP4 via ffmpeg, probes codecs with ffprobe,
and sends encrypted segments over DataChannel. Browser decrypts and
appends to SourceBuffer — playback starts within seconds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Complete browser-based client: Preact SPA with login, group file browser,
encrypted download, video playback, group chat, i18n, and dark/light theme.
Browser connects P2P to nodes behind residential NAT via WebRTC DataChannel
(aiortc). Hub handles signaling only — all data flows E2E.
Performance: pipelined downloads (8-chunk sliding window), binary msgpack
wire format (no base64), redundant I/O elimination. Large file downloads
stream to disk via File System Access API (showSaveFilePicker).
Validated on SFR + Orange residential NATs, Chrome + Firefox, IPv4/IPv6.
132 tests passing. Deployed to meshbay.org + Orange node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
|
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>
|