<p align="center">
<img src="assets/deadrop-logo.png" width="220" alt="deadrop logo" />
</p>
<h1 align="center">deadrop</h1>
<p align="center">
<b>Zeroβknowledge file drops that selfβdestruct.</b><br/>
One command. One link. Gone. Like it never happened.
</p>
<p align="center">
<a href="https://crates.io/crates/deadrop"><img src="https://img.shields.io/crates/v/deadrop.svg?style=flat-square&color=00ff88" alt="crates.io" /></a>
<a href="https://github.com/Karmanya03/Deadrop/releases"><img src="https://img.shields.io/github/v/release/Karmanya03/Deadrop?style=flat-square&color=00ff88" alt="release" /></a>
<a href="https://github.com/Karmanya03/Deadrop/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-00ff88?style=flat-square" alt="license" /></a>
<img src="https://img.shields.io/badge/encryption-XChaCha20--Poly1305-00ff88?style=flat-square" alt="encryption" />
<img src="https://img.shields.io/badge/written_in-Rust_π¦-00ff88?style=flat-square" alt="rust" />
</p>
<p align="center">
<img src="https://img.shields.io/badge/server_knows-nothing_π€·-ff4444?style=flat-square" alt="zero knowledge" />
<img src="https://img.shields.io/badge/after_download-π₯_self_destructs-ff4444?style=flat-square" alt="self destruct" />
<img src="https://img.shields.io/badge/dependencies-just_the_binary-blueviolet?style=flat-square" alt="single binary" />
</p>
---
## What is this?
Remember in spy movies when someone leaves a briefcase under a park bench, and someone else picks it up later? That's a **dead drop**.
This is that, but for files. And the briefcase is encrypted with military-grade cryptography. And the park bench self-destructs after pickup. And nobody β not even the bench β knows what's inside.
```
You Your friend
β β
β ded ./secret-plans.pdf β
β ββββββββββββββββββββββΊ β
β here's a link + QR code β
β β
β (sends link via Signal) β
β β
β opens link in browser
β browser decrypts locally
β downloads the file
β β
β π₯ file self-destructs β
β π server shuts down β
β β
β what file? there was no file. β
```
## Features
### Core
| π **Endβtoβend encrypted** | XChaCha20βPoly1305. The server never sees the key. Ever. |
| π **Key in URL fragment** | The `#key` part never hits server logs, proxies, or HTTP headers |
| π₯ **Selfβdestruct** | Expire by time, by download count, or both |
| π± **Works on phones** | Receiver only needs a browser. No app. No account. No signup. |
| π **Send folders** | Directories autoβpack to `.tar.gz` before encryption |
| βΎοΈ **Unlimited file size** | Streams from disk β your 50GB file won't eat your RAM |
| π **Optional password** | Argon2id key derivation (64MB memoryβhard, GPUβresistant) |
| π¦ **Single binary** | No runtime, no Docker, no config files. Just one executable. |
| π² **QR code** | Because typing URLs is for people who still use fax machines |
### Security Hardening
| π» **Fragment autoβclear** | `#key` is stripped from the URL bar and browser history the instant the page loads |
| π **IP pinning** | Download is locked to the first IP that connects β anyone else gets HTTP 403 |
| π‘ **Security headers** | CSP, `X-Frame-Options: DENY`, `no-referrer`, `no-cache`, antiβclickjack |
| β± **Rate limiting** | 2 req/sec per IP with burst of 5 β stops bruteβforce ID enumeration |
| π― **16βchar drop IDs** | ~2βΆβ΄ possible IDs β statistically impossible to guess |
| π **Constantβtime 404s** | Random delay on not-found responses prevents timingβbased ID enumeration |
| π₯ **Burn page** | Late visitors see "π₯ This drop was already downloaded and destroyed" instead of a generic 404 |
| β° **Autoβexpire page** | If the tab stays open past expiry, the key is nuked from JS memory and the UI selfβdestructs |
| π§ **Memory locking** | `mlock()` on Unix prevents the encryption key from being swapped to disk |
| π **Zeroβwrite deletion** | Encrypted temp files are overwritten with zeros before `rm` β no forensic recovery |
| π§Ή **Key zeroization** | Encryption key is wiped from RAM (via `zeroize`) on drop, both serverβside and inβbrowser |
## Installation
### π One-line install (Linux/macOS)
```bash
> Detects your OS & architecture automatically, downloads the right binary, and adds it to your PATH.
### Download a binary
Grab the latest release for your platform from [**Releases**](https://github.com/Karmanya03/Deadrop/releases).
| **Windows** | [`ded-windows-x86_64.exe`](https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-windows-x86_64.exe) | x86_64 |
| **Linux** | [`ded-linux-x86_64`](https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-linux-x86_64) | x86_64 (musl, static) |
| **Linux** | [`ded-linux-aarch64`](https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-linux-aarch64) | ARM64 (Raspberry Pi, etc.) |
| **macOS** | [`ded-macos-x86_64`](https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-macos-x86_64) | Intel |
| **macOS** | [`ded-macos-aarch64`](https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-macos-aarch64) | Apple Silicon (M1/M2/M3/M4) |
**Quick install (Linux/macOS):**
```bash
# Linux x86_64
curl -L https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-linux-x86_64 -o ded && chmod +x ded && sudo mv ded /usr/local/bin/
# macOS Apple Silicon
curl -L https://github.com/Karmanya03/Deadrop/releases/latest/download/ded-macos-aarch64 -o ded && chmod +x ded && sudo mv ded /usr/local/bin/
```
### Via cargo
```bash
cargo install deadrop
```
### Build from source
```bash
git clone https://github.com/Karmanya03/Deadrop.git
cd Deadrop
cargo build --release
# Binary at: target/release/ded
```
### π One-line update (Linux/macOS)
```bash
> Same as install β it overwrites the old binary with the latest release. Your PATH stays intact.
### π One-line uninstall (Linux/macOS)
```bash
rm -f ~/.local/bin/ded && echo "deadrop removed β "
```
> If you installed to `/usr/local/bin/` instead:
```bash
sudo rm -f /usr/local/bin/ded && echo "deadrop removed β "
```
### π Uninstall (cargo)
```bash
cargo uninstall deadrop
```
## Usage
### The basics
```bash
# Send a file
ded ./secret.pdf
# Send a folder
ded ./tax-returns-2025/
# That's it. That's the tool.
```
### The spicy options
```bash
# Self-destruct after 1 download, expire in 10 minutes
ded ./evidence.zip -n 1 -e 10m
# Password-protected (because you're paranoid, and that's ok)
ded ./passwords.csv --pw "correct-horse-battery-staple"
# Custom port
ded ./file.txt -p 4200
# No QR code (you hate fun)
ded ./file.txt --no-qr
# Go full Mission Impossible
ded ./plans.pdf -n 1 -e 30s --pw "this-message-will-self-destruct"
```
### What you see
```
βββββββ ββββββββ ββββββ βββββββ βββββββ βββββββ βββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ βββββββββ βββββββββββ ββββββββββββββ βββββββββββ
βββ βββββββββ βββββββββββ ββββββββββββββ ββββββββββ
βββββββββββββββββββ ββββββββββββββ βββββββββββββββ
βββββββ βββββββββββ ββββββββββ βββ βββ βββββββ βββ
β‘ zero-knowledge encrypted file sharing β‘
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β URL http://192.168.1.42:8080/d/a3f9c1b2#xK9m β
β β
β ββ File secret.pdf β
β ββ Size 4.2 MB β
β ββ Expires 10m β
β ββ Downloads 1 β
β ββ Crypto XChaCha20-Poly1305 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββ ββββββ βββββββ <- QR code appears here
β βββ β βββ ββ β βββ β scan with phone
...
```
### What the receiver sees
A clean, dark download page in their browser. Click **"Download & Decrypt"** β file decrypts locally in their browser via WebAssembly β downloads to their device. The server never touches the plaintext.
## Flags Cheat Sheet
| `--port` | `-p` | `8080` | Port to listen on |
| `--expire` | `-e` | `1h` | Autoβexpire (`30s`, `10m`, `1h`, `7d`) |
| `--downloads` | `-n` | `1` | Max downloads before selfβdestruct (0 = β) |
| `--pw` | β | None | Require password (Argon2id derived) |
| `--bind` | `-b` | `0.0.0.0` | Bind address |
| `--no-qr` | β | `false` | Hide QR code |
## How It Works
```
Sender Server (your machine) Receiver
β β β
β 1. Encrypt file with random key β β
β 2. Store ciphertext on disk β β
β 3. Key goes in URL #fragment β β
β βββββββββββββββββββββββββββββββΊ β β
β β 4. Receiver opens URL β
β β ββββββββββββββββββββββββββββββββ
β β 5. Serve encrypted blob β
β β βββββββββββββββββββββββββββββββΊβ
β β β
β β 6. Browser extracts #key β
β β (stripped from URL instantly)β
β β 7. WASM decrypts locally β
β β 8. File downloads β
β β 9. Key wiped from JS memory β
β β β
β β π₯ Self-destruct β
β β π₯ Drop marked as burned β
β β π Server shuts down β
```
**The critical insight**: the `#fragment` in a URL is **never sent to the server**. Not in HTTP requests, not in logs, not in referrer headers. The server literally cannot learn the key even if it tried.
## Security Architecture
### Defense in Depth
```
Layer 1: Encryption XChaCha20-Poly1305 (256-bit key, AEAD)
Layer 2: Zero-knowledge Key in URL fragment β server never sees it
Layer 3: Network Security headers, CSP, no-referrer, no-cache
Layer 4: Access control IP pinning + rate limiting + 16-char drop IDs
Layer 5: Anti-forensics mlock() + zeroize + zero-write disk deletion
Layer 6: Browser Fragment auto-clear + auto-expire + key wipe
Layer 7: Self-destruct One download β burn page β server shutdown
```
### Threat Model
#### β
Protected against
| Server operator learning file contents | Zeroβknowledge β key never reaches server |
| Manβinβtheβmiddle reading the key | Key lives in `#fragment`, never transmitted over HTTP |
| Server logs leaking the key | Fragments aren't logged by any HTTP server or proxy |
| Brute force on encryption | XChaCha20-Poly1305 with 256βbit keys |
| GPU attacks on passwords | Argon2id with 64MB memory cost |
| Drop ID enumeration | 16βchar IDs (~2βΆβ΄) + rate limiting + constantβtime 404s |
| URL bar shoulder surfing | Fragment stripped from URL bar on page load |
| Browser history forensics | `history.replaceState()` removes the `#key` |
| Key persisting in RAM | `zeroize` on Rust side, `key = null` on JS side |
| Key swapped to disk (Unix) | `mlock()` pins key memory pages |
| Encrypted file recovery | Zeroβoverwrite before deletion |
| Clickjacking / iframe embedding | `X-Frame-Options: DENY` + `frame-ancestors 'none'` |
| XSS injection | Content Security Policy β scripts only from `'self'` |
| Stale tab leaking key | Autoβexpire nukes key from memory when drop expires |
| Late visitor confusion | Burn page β "already downloaded and destroyed" |
#### β NOT protected against
- Someone who has the full URL with the `#key` (that IS the key)
- Malware on sender/receiver device (keyloggers, screen capture)
- Your friend screenshotting the file and posting it on Twitter
- Rubber hose cryptanalysis (look it up, it's not pretty)
- Time travelers
## Technical Details
| Encryption | XChaCha20βPoly1305 | 256βbit, extended nonce, AEAD. Used by WireGuard, Cloudflare, etc. |
| KDF | Argon2id | Memoryβhard, GPUβresistant. Winner of the Password Hashing Competition |
| Chunk size | 64KB | Balances streaming performance vs. auth tag overhead |
| Server | Axum (Rust) | Async, zero-copy, no garbage collector |
| Rate limiter | tower_governor | Token bucket per IP β prevents brute force |
| Browser crypto | WebAssembly | Same Rust code compiled to WASM, runs in-browser at near-native speed |
| Nonce derivation | base XOR chunk_index | Per-chunk unique nonces without storing them |
| Binary embedding | rust-embed | HTML, CSS, JS, WASM all baked into the single binary |
| Memory safety | mlock + zeroize | Key never hits swap, wiped from RAM on drop |
## Memory Usage
| 1 MB | ~5 MB | ~5 MB |
| 100 MB | ~5 MB | ~200 MB |
| 1 GB | ~5 MB | ~2 GB (desktop) |
| 10 GB | ~5 MB | Desktop only (streaming) |
The server uses constant memory regardless of file size. It streams encrypted chunks from disk.
## FAQ
**Q: Is this legal?**
A: It's a file sharing tool with encryption. Like Signal, or HTTPS, or putting a letter in an envelope. What you put inside is your business.
**Q: Can I use this at work?**
A: Your IT department will either love you or fire you. No in-between.
**Q: Why not just use Google Drive?**
A: Google Drive knows your files. Deadrop doesn't. That's the whole point.
**Q: What happens if I lose the URL?**
A: The file is gone. That's... the feature. It's a dead drop, not Google Photos.
**Q: Can the server operator see my files?**
A: No. The encryption key is in the URL fragment which never reaches the server. The server only holds meaningless encrypted bytes.
**Q: What if someone else tries to download with the link?**
A: They can't. The download is IP-pinned to the first device that connects. A second IP gets blocked with HTTP 403.
**Q: What if I visit a dead link?**
A: If the file was already downloaded, you'll see a burn page: "π₯ This drop was already downloaded and destroyed." If it expired, you get a standard not-found message.
**Q: Why Rust?**
A: Because we wanted the binary to be fast, safe, and have zero dependencies. Also because we enjoy fighting the borrow checker on Friday nights.
## Contributing
PRs welcome. Here's what's on the radar:
- [ ] Receiverβside streaming decryption for huge files on mobile
- [ ] Builtβin HTTPS (rustls + autoβgenerated certs)
- [ ] `ded receive` mode (pull instead of push)
- [ ] Clipboard mode (`echo "secret" | ded -`)
- [ ] Tor hidden service mode
- [ ] Multiβfile drops
- [ ] Web UI upload mode
## License
MIT β do whatever you want. Just don't blame us if your dead drop gets intercepted by actual spies.
---
<p align="center">
<sub>Built with π¦ and paranoia.</sub>
</p>