End-to-end encrypted, zero-knowledge file sharing.
Your files. Your keys. Absolute privacy.
Features • Security Model • Architecture • Protocol • Getting Started • Self-Hosting • License
VoidDrop is a file-sharing platform built on a single principle: the server should know nothing about your data. Files are encrypted entirely on the client side before they ever leave your browser. The encryption key exists only in the URL fragment (#secret), which is never sent to the server by design of the HTTP protocol. VoidDrop focuses entirely on direct, secure Peer-to-Peer (P2P) transfers for absolute privacy. All file transfers occur directly between the sender and receiver without any intermediary storage. The server functions exclusively as a transient WebRTC signaling channel and retains zero knowledge of the keys, file names, or contents.
JSZip) for legacy platforms.crypto-worker module) — constant memory usage regardless of file size.seq) per peer connection. The client discards any out-of-order or duplicated signals, preventing transaction replay attacks.sodium.memzero() immediately upon session termination, preventing key-extraction from browser RAM.crypto.getRandomValues() instead of fixed zero-nonces, eliminating key-stream reuse vulnerabilities./metrics endpoint in the Axum backend to track concurrent rooms, active WebSocket connections, rate limits, and network throughput without capturing personal user data.| Information | Server Access |
|---|---|
| File contents | ❌ Never (encrypted client-side) |
| File names / metadata | ❌ Never (encrypted in manifest) |
| Decryption key | ❌ Never (URL #fragment is not sent to server) |
| Who uploaded | ❌ Ephemeral Ed25519 pubkey, scrubbed from memory after session close |
| Who downloaded | ❌ No authentication required for download |
| P2P file data | ❌ Never touches the server |
| Signaling content | ❌ CBOR + HMAC-signed (server relays opaque blobs) |
| Information | Why |
|---|---|
| IP addresses | Inherent in TCP connections (use Tor/VPN to mitigate) |
VoidDrop is designed to protect against:
sodium.memzero() memory scrubbing on session cleanup[!NOTE] The security posture of VoidDrop is fully cataloged using the STRIDE threat modeling framework in threat_model.md.
In addition, the P2P signaling protocol has been formally verified using ProVerif via Applied Pi-Calculus. The formal specification is located at voiddrop.pv.
graph TB
subgraph CLIENT ["Browser / Tauri App"]
UI["SvelteKit 5 UI\nTauri Dialogs & Native FS"]
CW["Crypto Web Worker\nlibsodium WASM + ML-KEM-768"]
WR["WebRTC Engine\nPerfect Negotiation + HMAC"]
UI <--> CW
UI <--> WR
end
WR -- "P2P Mode\n(signaling & rate limits)" --> WS
subgraph SERVER ["Server"]
WS["WebSocket Relay (Axum)\nOpaque relay\nUUID rooms, max 1000"]
PM["Prometheus Telemetry\nEndpoint: /metrics"]
WS --> PM
end
VoidDrop uses a custom binary container format specified in Protocol v1.
The PSK is always extracted from the URL fragment (#<hex>). It never leaves the browser.
All data and signaling keys are derived using HKDF-SHA-256:
PSK (32 bytes, from URL fragment — never sent to server)
│
├── HKDF-SHA-256(PSK, "signaling-mac") → k_hmac (Signaling HMAC integrity)
│
└── Mixed with SharedSecret (ML-KEM-768 + X25519 hybrid exchange)
│
├── HKDF-SHA-256(PSK || SharedSecret, "manifest") → k_manifest
└── HKDF-SHA-256(PSK || SharedSecret, "p2p-seg-base") → k_stream
| Section | Size | Contents |
|---|---|---|
| Global Header | 26 bytes | Magic (VDDP01), version, cipher suite, segment size, manifest length |
| Encrypted Manifest | Variable | XChaCha20-Poly1305 sealed CBOR: file names, sizes, bundle structure. AAD = Global Header. Includes prepended random 24-byte nonce. |
| Data Stream | Variable | Custom framed stream cipher: 32KB frames, length-prefixed, AEAD per frame, TAG_FINAL on last |
| Purpose | Algorithm | Implementation |
|---|---|---|
| Manifest encryption | XChaCha20-Poly1305 (AEAD) | Rust WASM (crypto-worker) with randomized nonces |
| Stream encryption | Custom framed stream cipher (XChaCha20-Poly1305) | Rust WASM (crypto-worker) |
| Key derivation | HKDF-SHA-256 | WebCrypto API |
| Signaling MAC | HMAC-SHA-256 | WebCrypto API |
| Key exchange (P2P) | X25519 + ML-KEM-768 | libsodium + custom WASM |
| Upload signing | Ed25519 | WebCrypto API |
| Signaling encoding | CBOR | cbor-x |
| Memory Scrubbing | libsodium memzero | libsodium-wrappers |
$state Class properties)/ws/{room_id})tracing (structured diagnostic logging)/metrics collector endpointturn.your-domain.com for VPN/symmetric NAT traversal)unsafe-inline via nonce hashes)docker compose up -d
This starts the Coturn TURN server and Caddy reverse proxy.
cd crypto-worker
wasm-pack build --target web
cd backend
cargo run
The backend runs a lightweight, high-performance WebSocket relay server on http://localhost:3300 (or falls back to 3301 if WSAEACCES port exclusion occurs on Windows). It also serves Prometheus telemetry metrics on /metrics.
cd frontend
cp .env.example .env # set PUBLIC_API_BASE
npm install
npm run dev
Starts on http://localhost:5173.
VoidDrop is designed to be self-hosted. The server is intentionally "blind" — even a malicious host cannot access your files.
backend/.env)# CORS origins (comma-separated)
CORS_ORIGINS=https://your-domain.com,http://localhost:5173
# Maximum concurrent rooms (default: 1000)
MAX_ROOMS=1000
frontend/.env)PUBLIC_API_BASE=https://api.your-domain.com
# Optional: TURN server for NAT traversal
PUBLIC_TURN_URL=turn:your-server:3478
PUBLIC_TURN_USERNAME=turnuser
PUBLIC_TURN_CREDENTIAL=your-turn-password
CORS_ORIGINS and MAX_ROOMS in backend/.envPUBLIC_API_BASE in frontend/.envnpm run buildcargo run --releaseVoidDrop/
├── backend/ # Rust WebSocket relay
│ └── src/
│ ├── main.rs # App state, room GC, router, port fallback, Prometheus telemetry metrics
│ ├── middleware.rs # Rate limiting (token bucket per IP)
│ ├── tests.rs # 19 backend integration tests (CORS, Rate limits, GC)
│ └── handlers/
│ ├── ws.rs # WebSocket signaling relay
│ └── turn.rs # Dynamic TURN credentials generator with tracing logs
├── frontend/ # SvelteKit web & Tauri application
│ ├── src-tauri/ # Tauri native desktop integration
│ └── src/
│ ├── hooks.server.ts # Security headers (Strict Content Security Policy)
│ ├── lib/
│ │ ├── components/ # Reusable modular UI components
│ │ │ ├── TitleBar.svelte # App header window controls
│ │ │ ├── ThemeToggle.svelte # Light/Dark mode switcher
│ │ │ ├── SenderView.svelte # File dropping & transmission dashboard
│ │ │ ├── ReceiverView.svelte # Room selection & download console
│ │ │ ├── DropZone.svelte # Drag and drop overlay
│ │ │ ├── SpecModal.svelte # Detailed crypto specs and diagnostic logs
│ │ │ ├── LayoutErrorBoundary.svelte # Svelte 5 global crash handler
│ │ │ ├── FileExplorer.svelte # Unified file tree list (sender + receiver)
│ │ │ ├── ConnectionLog.svelte # Transfer logs + progress tracking
│ │ │ ├── ManifestPreview.svelte # File preview before downloading
│ │ │ ├── QrCode.svelte # P2P session QR generation
│ │ │ └── TransferDashboard.svelte # Interactive stats overlay & transfer summary
│ │ ├── transfer/ # Transfer engine modules
│ │ │ ├── transferState.svelte.ts # Native Svelte 5 State Class ($state)
│ │ │ ├── senderEngine.ts # File chunking, P2P channels init
│ │ │ ├── receiverEngine.ts # File writing, stream slicing, ZIP packager
│ │ │ └── cryptoOrchestrator.ts # Worker messages & signaling broker
│ │ ├── worker/ # Crypto Web Worker (libsodium + ML-KEM WASM)
│ │ │ ├── crypto.worker.ts # Background thread encrypting/decrypting data
│ │ │ └── service-worker.ts # Offline PWA TypeScript service worker
│ │ ├── network/ # WebRTC P2P engine with sequence checking
│ │ ├── i18n.svelte.ts # Minimal localization dictionary wrapper
│ │ ├── identity.ts # Ed25519 ephemeral identity keys
│ │ ├── isTauri.ts # Desktop environment detector
│ │ ├── tauriFs.ts # Native recursive directory traversing
│ │ └── fileTree.ts # File/directory tree parser & central junk-file filter
│ └── routes/
│ └── +page.svelte # Main application entry point (decomposed)
├── crypto-worker/ # ML-KEM-768 WASM module (Rust)
├── docs/ # Project specifications & threat models
│ ├── protocol_v1_spec.md # VoidDrop Protocol v1 specification
│ ├── threat_model.md # Comprehensive STRIDE threat model
│ ├── verification/
│ │ └── voiddrop.pv # Applied Pi-Calculus ProVerif formal model
│ └── fix_docker.ps1 # Windows docker network Coturn workaround
├── docker-compose.yml # Pure Coturn & Caddy services (no Postgres/MinIO dependencies)
└── Caddyfile # Reverse proxy config
| Protection | Implementation |
|---|---|---|
| WebSocket limits | Messages > 256 KB → immediate disconnect (hardened in E2E tests) |
| Room ID validation | UUID format required, max 1,000 concurrent rooms |
| Rate limiting | Token bucket per IP via lock-free DashMap, IPv6 grouped by /64 prefix |
| Error sanitization | Internal errors logged server-side via tracing, generic messages to client |
| Truncation detection | TAG_FINAL validation on last stream chunk |
| Security headers | CSP (no unsafe-inline), X-Frame-Options DENY, no-referrer, nosniff, Permissions-Policy |
| Ephemeral Rooms | Automatic garbage collector prunes empty rooms every 5 minutes |
| Screen Wake Lock | Prevents mobile OS from killing transfers when screen turns off |
| Isolated Folders | Isolated directory creation prevents local file overwrites |
| Thread-Safety | TOCTOU check-and-insert race conditions resolved in AppState |
cd frontend
npx vitest run
197 tests across 16 test files:
| Test Suite | Tests | Coverage |
|---|---|---|
crypto.test.ts |
15 | Crypto roundtrips, key derivation, AAD binding, TAG_FINAL, tampered data |
manifest.test.ts |
19 | Manifest CBOR encoding/decoding, zero-byte files, Unicode, large bundles |
e2e-crypto.test.ts |
10 | Full encrypt→decrypt pipeline: single files, bundles, wrong keys, 1MB integrity |
fileTree.test.ts |
30 | File tree building, filtering, sorting, manifest builder, size estimation |
senderEngine.test.ts |
13 | File chunking with batching, zero-byte files, data integrity |
receiverEngine.test.ts |
19 | Chunk slicing across file boundaries, write queue, re-entrancy protection |
webrtc.test.ts |
27 | Frame protocol, Global Header format, ICE/TURN fallback, WS URL derivation, Signal Sequence tracking |
webrtc-hmac.test.ts |
7 | HMAC sign/verify, tamper detection, key isolation |
psk.test.ts |
15 | PSK hex parsing and validation |
stream-deframing.test.ts |
12 | Frame splitting, buffering, retry |
identity.test.ts |
3 | Ed25519 keypair generation, payload signing |
isTauri.test.ts |
3 | Checks Tauri desktop environment flags |
QrCode.test.ts |
3 | Validates QR code generation and link binding |
originTranslation.test.ts |
5 | Verifies window location and origin translation helpers |
localDiscovery.test.ts |
7 | Verifies local LAN peer multicast discovery |
tauriFs.test.ts |
9 | Tests Tauri native filesystem writing bridge |
cd backend
cargo test
19 integration tests covering:
/metrics payload formattingcd frontend
npx playwright test
14 E2E tests covering full end-to-end P2P file transfers:
unsafe-inline)$state refactoringsodium.memzero)Contributions are welcome. Please open an issue before submitting a PR for significant changes.
Core principle: the server must remain blind. Any change that gives the server access to plaintext data, file metadata, or user identity will be rejected.
This project is licensed under the GNU Affero General Public License v3.0.
Built with paranoia. Trust no server — not even this one.