diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-13 04:10:14 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-13 04:10:14 +0200 |
| commit | ed9fb22ed703db38f9b07c00d17076f90aa4cbc8 (patch) | |
| tree | 448af8bdc7734798607bb33735c2a8439c362c13 /packages/meshbay-node/src/meshbay_node/daemon.py | |
| parent | ee6573c57f721db8550e34e1c1c79c5922c62a4b (diff) | |
| download | meshbay-ed9fb22ed703db38f9b07c00d17076f90aa4cbc8.tar.gz | |
fix(node)!: remove unauthenticated HTTP file API and TCP transport
Phase 11.5.A — findings C1 and C6 (see second-review.md).
C1: the per-group HTTP file API bound 0.0.0.0 for every configured group,
private ones included, and served two endpoints with no authentication at all:
GET /index (full Mesh Group Index) and GET /file/{id} (raw plaintext file via
FileResponse). Anyone able to reach the port — LAN, forwarded port, permissive
IPv6 — read every private file. This bypassed the entire GEK-proof and node
sovereignty layer. Deleted rather than patched: it duplicated MNP without any
of its controls.
C6: the TCP+TLS chunk server accepted a bare JWT with no GEK proof, leaving a
second non-compliant handshake path. Deleted; QUIC remains and will be brought
to parity with WebRTC by the unified handshake in 11.5.4.
Transport decision recorded in transport/__init__.py: WebRTC/ICE is primary for
browser and native clients (the only NAT traversal validated here — 2 ISPs,
IPv4 STUN + IPv6, 4G CGNAT); QUIC is kept for LAN, port-forwarded and hub-less
group:// access. punch_nat() is a direct-connection helper, not a traversal
stack.
Also removed server_ssl_context()/client_ssl_context() from tls_cert.py (no
remaining callers) and a dead import of the former in quic_server.py.
generate_self_signed_cert() stays: QUIC uses it, and the certificate hash is
the intended channel-binding anchor for 11.5.6, since QUIC has no DTLS
fingerprint to bind the GEK proof to.
BREAKING CHANGE: node.toml keys `port` and `http_port` are gone. Regenerate
config with `meshbay-node init`. Env var MESHBAY_PORT -> MESHBAY_QUIC_PORT.
Tests: 198 passed (209 - 7 test_http_server - 4 test_transport). No other test
changed status. Net -1300 lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/daemon.py | 61 |
1 files changed, 8 insertions, 53 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index fe12909..c930e54 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -8,9 +8,9 @@ Startup sequence: 4. Fetch GEK bundle from hub (if group configured) 5. Start directory indexer (watchdog) 6. Create chat stores (one SQLite DB per group) - 7. Create WebRTC transport (browser clients via DataChannel) - 8. Start QUIC+TCP chunk servers (native clients) - 9. Start HTTP file API (public content) + 7. Create WebRTC transport (browser + native clients via DataChannel) + 8. Start QUIC chunk server (LAN / port-forwarded / hub-less direct access) + 9. (Phase 11.5: the unauthenticated HTTP file API and the TCP+TLS server were removed) 10. Start hub WebSocket (signaling, revocations, WebRTC offers) 11. Start local web UI on node.ui_port (localhost only) 12. Run until SIGINT/SIGTERM @@ -43,11 +43,9 @@ from meshbay_node.hub_client import HubClient, HubConfig from meshbay_node.indexer import DirectoryIndexer from meshbay_node.keystore import NodeKeys, load_or_create_keystore from meshbay_node.transport import ( - ChunkServer, Denylist, QUIC_AVAILABLE, WEBRTC_AVAILABLE, - create_http_app, ) if QUIC_AVAILABLE: @@ -103,12 +101,10 @@ class NodeDaemon: "hub_url": config.hub.url, "username": config.hub.username, "groups": [g.name for g in config.groups], - "node_port": config.node.port, "quic_port": config.node.quic_port, "endpoint_hint": None, "indexes": {}, } - self._tcp_server: ChunkServer | None = None self._quic_server = None self._webrtc = None self._denylist = Denylist() if Denylist else None @@ -118,7 +114,6 @@ class NodeDaemon: self._indexers: list[DirectoryIndexer] = [] self._tasks: list[asyncio.Task] = [] self._hub: HubClient | None = None - self._http_servers: list[uvicorn.Server] = [] async def run(self) -> None: log.info("MeshBay Node starting up") @@ -265,7 +260,7 @@ class NodeDaemon: else: log.warning("WebRTC not available (aiortc not installed)") - # 7. QUIC + TCP chunk servers + # 7. QUIC chunk server (LAN / port-forwarded / hub-less direct access) if QUIC_AVAILABLE: self._quic_server = QuicChunkServer( sk_node=keys.sk_ed25519, @@ -282,19 +277,6 @@ class NodeDaemon: log.info("QUIC server on port %d (%d groups)", self._config.node.quic_port, len(groups_ctx)) - self._tcp_server = ChunkServer( - sk_node=keys.sk_ed25519, - hub_pk_pem=session.hub_pk_pem, - gek=first["gek"], - shared_root=first["shared_root"], - index=first["index"], - host="0.0.0.0", - port=self._config.node.port, - groups=groups_ctx, - ) - await self._tcp_server.start() - log.info("TCP+TLS server on port %d", self._config.node.port) - # 8. Hub WebSocket (signaling + revocations + WebRTC offers) async def on_webrtc_offer(sdp, peer_id, ice_candidates): if not self._webrtc: @@ -338,32 +320,10 @@ class NodeDaemon: self._tasks.append(ws_task) log.info("Hub WS task started") - # 9. HTTP file API (one per group) - for gid, gctx in groups_ctx.items(): - group_cfg = next( - (g for g in self._config.groups if g.id == gid), None) - if not group_cfg: - continue - http_app = create_http_app( - sk_node=keys.sk_ed25519, - hub_pk_pem=session.hub_pk_pem, - shared_root=gctx["shared_root"], - index=gctx["index"], - group_id=gid, - group_name=group_cfg.name, - gek=gctx.get("gek"), - ) - http_cfg = uvicorn.Config( - http_app, - host="0.0.0.0", - port=group_cfg.http_port, - log_level="warning", - ) - http_server = uvicorn.Server(http_cfg) - self._http_servers.append(http_server) - self._tasks.append(asyncio.create_task(http_server.serve())) - log.info("HTTP API on port %d for group %s", - group_cfg.http_port, group_cfg.name) + # 9. (removed in Phase 11.5) The per-group HTTP file API used to start here. + # It served the Mesh Group Index and raw plaintext files on 0.0.0.0 with no + # authentication, for private groups too — finding C1. Every client path now + # goes through the MNP handshake (JWT + group claim + GEK proof). # 10. Update admin UI state (UI already running from step 2) self._state["groups_ctx"] = groups_ctx @@ -541,11 +501,6 @@ class NodeDaemon: if self._quic_server: await self._quic_server.stop() - if self._tcp_server: - await self._tcp_server.stop() - - for server in self._http_servers: - server.should_exit = True log.info("Node stopped") |