Serverless, end-to-end encrypted, peer-to-peer file sharing β in your browser and your terminal.
Files travel machine-to-machine. No uploads. No accounts. No file ever touches a server.
π Try the web app Β· π¦ npm i -g mesh-share Β· π§ How it works
Servers coordinate. Peers transfer. The rendezvous infrastructure only ever sees tiny control messages; when a relay is unavoidable it forwards ciphertext it cannot read. File content exists on exactly two machines: yours and theirs.
| π Web β mesh-share.vercel.app | β¨οΈ CLI β mesh-share on npm |
|
|---|---|---|
| Share via | 4-letter room code + QR | Self-contained 84-char share code |
| Transport | WebRTC DataChannel (DTLS 1.3) | From-scratch stack: Kademlia DHT Β· STUN Β· TURN Β· reliable-UDP |
| Peer discovery | Room on signaling server | Distributed hash table (BitTorrent-style) |
| Encryption | DTLS (built into WebRTC) | X25519 ECDH β HKDF β AES-256-GCM (hand-wired) |
| Integrity | SHA-256 chunks + Merkle proofs | SHA-256 chunks + Merkle proofs |
| Install | nothing β open a tab | npm install -g mesh-share |
# the entire CLI experience:
you: mesh send movie.mp4
β AIB2Z-ROQMU-H2DQF-ILAA5-...
anyone, anywhere: mesh receive AIB2ZROQMUH2DQFILAA5...
β movie.mp4 β 500/500 chunks verified
Open mesh-share.vercel.app β drop a file β share the code. That's it.
npm install -g mesh-share
mesh send ./anything.zip # zero flags β prints a share code
mesh receive <SHARE-CODE> # on any machine, any network
mesh diagnose # what can your NAT do?sequenceDiagram
participant A as Sender (browser)
participant S as Signaling server
participant B as Receiver (browser)
A->>S: CREATE_ROOM β code "WLF4" (+ 24h TURN credentials)
B->>S: JOIN_ROOM "WLF4"
A-->>B: SDP offer/answer + ICE candidates (relayed, opaque to server)
A->>B: WebRTC DataChannel β DTLS encrypted, direct when possible
Note over A,B: chunks + Merkle proofs, verified piece-by-piece
flowchart LR
subgraph SEND["mesh send"]
IDX["SHA-256 chunks<br/>+ Merkle tree"] --> SEED["chunk server<br/>+ TURN allocation"]
SEED --> ANN["announce on<br/>Kademlia DHT"]
end
ANN -. "share code<br/>(addresses + content hash)" .-> DISC
subgraph RECV["mesh receive"]
DISC["DHT lookup<br/>O(log N)"] --> LADDER{"connection<br/>ladder"}
LADDER -->|"tier 1"| DIRECT["direct TCP<br/>(LAN / UPnP / public)"]
LADDER -->|"tier 2"| RELAY["TURN relay-to-relay<br/>(ciphertext only)"]
DIRECT --> VERIFY["decrypt Β· hash Β·<br/>Merkle-verify Β· write"]
RELAY --> VERIFY
end
Every layer of the CLI's network stack is implemented by hand in packages/engine:
- Kademlia DHT β XOR-metric routing, k-buckets, iterative
O(log N)lookups, 90s liveness TTL (dht.js) - STUN client β RFC 5389 binding, XOR-MAPPED-ADDRESS decoding (
net/stun.js) - TURN client β RFC 5766 allocations, permissions, HMAC credentials (
net/turn.js) - UPnP β SSDP discovery + SOAP port mapping (
net/upnp.js) - Reliable-UDP β sliding-window ARQ with retransmit, reordering, backpressure (
net/reliableDatagram.js) - E2E crypto β ephemeral X25519 β HKDF β AES-256-GCM per connection (
crypto.js) - Merkle verification β every chunk ships a
logβ(N)-hash proof chained to the share code's root - Multi-peer swarm β up to 30 seeders, pipelined requests, misbehaving peers evicted after 5 strikes (
swarm.js) - Self-healing transfers β retransmits back off exponentially through mobile-network stalls, and if every connection drops the receiver re-discovers peers via the DHT and continues from the last verified chunk (
transfer.js) - Pause/resume β
Ctrl+Ccheckpoints to a.meshstatesidecar; re-run the same command to continue
| Runtime dependencies in the published CLI | 0 |
| Published package | 3 files, ~500 kB (single esbuild bundle) |
| Automated tests across engine / CLI / signaling | 180+ (including a seeder killed and resurrected mid-transfer) |
| Connection tiers | direct TCP β TURN relay (hole-punch tier on the roadmap) |
| Chunk size | adaptive 64 kB β 32 MB (β€ ~50k chunks per file) |
| Merkle proof per chunk | logβ(N) hashes β ~300 bytes for a 500-chunk file |
| Largest real-world web transfer tested | 1.8 GB (streamed to disk, no RAM blow-up) |
| Encryption | X25519 ECDH + HKDF-SHA256 + AES-256-GCM Β· DTLS 1.3 on web |
packages/
βββ engine/ the from-scratch P2P stack (DHT, STUN, TURN, UPnP, reliable-UDP,
β Merkle, swarm, resume) β zero dependencies, 19 test suites
βββ cli/ `mesh` command β published to npm as mesh-share
βββ signaling/ WebSocket rooms + TURN credential minting (Docker)
βββ web/ React 19 + Vite + zustand client β deployed on Vercel
screenshots/ images used by the READMEs
docker-compose.yml signaling + Caddy (TLS) + coturn β one command deploys the backend
Everything server-side runs from one compose file on any VM:
cp .env.example .env # set EXTERNAL_IP, PRIVATE_IP, TURN_SECRET, TURN_REALM
docker compose up -d # signaling + Caddy TLS + coturn (host networking)
nohup mesh daemon & # DHT bootstrap node (or install it as a systemd unit)Open these inbound ports: 80,443/tcp (TLS), 8080/tcp (signaling), 3478/udp+tcp + 49160-49200/udp (TURN), 4001/udp (DHT bootstrap).
Point clients at your box with --bootstrap <ip>:4001 --turn-host <ip> --turn-secret <secret> or the MESH_* env vars β the defaults baked into the npm package are just a public courtesy instance.
Why does a P2P app have servers at all? The same reason BitTorrent ships bootstrap routers and Tailscale runs DERP relays: something public must introduce two hidden machines, and when both peers are behind hostile NAT, physics requires a relay. Mesh's relay forwards ciphertext only, and every layer prefers a direct path first.
npm test # all workspaces
npm test -w packages/engine # loopback DHT meshes, swarm failure injection,
# TURN message encoding, resume, reliable-UDP
npm test -w packages/cli # spawns the real binary end-to-end180+ tests β including a network-blackout survival test and one that kills the only seeder mid-transfer, resurrects it, and asserts the file still arrives byte-identical β plus real-world verification: cross-continent transfers (India β Azure), CGNAT and mobile-hotspot relay paths, and fresh-machine installs from the public registry.
- UDP hole punching for the CLI β direct NATβNAT connections with TURN as last resort (the web already gets this via ICE)
- Multiple default bootstrap nodes, raced in parallel
- Share-code v3: IPv6 candidates
- Skip the download entirely when the output file already matches the Merkle root ("you already have this file")
- AIMD congestion control in the reliable-UDP layer
- Authenticated key exchange bound to the share code
Mesh is open source and PRs are welcome β the codebase is deliberately dependency-light and every protocol layer is readable in one sitting. Start with packages/engine/src/net/connect.js (the connection ladder) to get oriented, run npm test, and open an issue or PR.
ISC Β© Subhodeep Samanta





