deadrop 0.1.3

Zero-knowledge encrypted dead drop. One binary. One command. Gone.
Documentation
<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

| Feature | Description |
|---|---|
| πŸ” **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

| Feature | Description |
|---|---|
| πŸ‘» **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
curl -fsSL https://raw.githubusercontent.com/Karmanya03/Deadrop/main/install.sh | 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).

| Platform | Binary | Architecture |
|---|---|---|
| **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
curl -fsSL https://raw.githubusercontent.com/Karmanya03/Deadrop/main/install.sh | 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

| Flag | Short | Default | What it does |
|---|---|---|---|
| `--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

| Threat | How |
|---|---|
| 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

| Component | Choice | Why |
|---|---|---|
| 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

| File Size | Server RAM (Sender) | Browser RAM (Receiver) |
|---|---|---|
| 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>