SlothBox

How it works

The full pipeline, every layer.

Read this once and you'll know whether to trust the architecture. Every step from “drop the file” to “your recipient downloads it” is here, plus what each server in the path does, plus the grep commands to verify each claim against the open-source code.

01 · the data flow

From your browser to your recipient's, byte by byte.

  YOUR BROWSER                          SLOTHBOX SERVERS               RECIPIENT BROWSER
  ──────────────                         ─────────────────                ──────────────────

  1. drop file
        │
        ▼
  2. browser generates 256-bit key  ◄──── never leaves this device ────
        │
        ▼
  3. split file into 5 MiB chunks
        │
        ▼
  4. seal each chunk with
     XChaCha20-Poly1305 (libsodium)
        │
        ▼
  5. POST /api/shares  ─────────────►  Caddy ─► Hono gateway
                                            │
                                            ▼
                                       Postgres 16: insert metadata,
                                       allocate shortId
                                            │
        ◄─── shortId + uploadUrls ──────────┘
        │
        ▼
  6. PUT each ciphertext chunk  ────►  Caddy ─► .NET ingest (Kestrel)
                                            │
                                            ▼
                                       MinIO: write encrypted blob
                                            │
                                       Postgres: record share_chunks row
                                            │
        ◄─── 201 per chunk ─────────────────┘
        │
        ▼
  7. share the URL — key lives in #fragment
                                                                     │
                                                                     ▼
                                                                8. recipient
                                                                   opens URL
                                                                     │
                                                                     ▼
                                                                9. fragment
                                                                   stays
                                                                   client-
                                                                   side,
                                                                   key extracted
                                                                     │
                                            ◄── GET /api/shares ─────┘
                                       Hono: serve metadata
                                       (encryptedMeta + nonceMeta)
                                            │
                                            ▼
                                       ──── ciphertext meta ────────►
                                                                     │
                                                                10. decrypt meta,
                                                                    get filename
                                                                     │
                                            ◄── GET /chunk/:id/:N ───┘
                                       .NET ingest: stream from MinIO
                                            │
                                            ▼
                                       mark_chunk_served() in Postgres
                                       (if burn=ON AND last chunk:
                                        flip state=destroyed,
                                        append audit_chain entry,
                                        all in one transaction)
                                            │
                                       ──── ciphertext chunk ───────►
                                                                     │
                                                                11. decrypt chunk
                                                                    (XChaCha20
                                                                     Poly1305 tag
                                                                     verifies
                                                                     integrity)
                                                                     │
                                                                12. assemble file,
                                                                    trigger
                                                                    download
                                            ◄── Reaper sweep (≤60s) ─
                                       deletes MinIO blob,
                                       deletes share_chunks rows,
                                       writes second audit_chain entry

Every arrow is a real network hop or function call you can find in the source. The dashed labels are constraints — “never leaves this device” means the key is generated by your browser's RNG and stored in tab memory; it doesn't touch our servers, it doesn't touch localStorage, it lives only in the URL fragment which browsers refuse to send to any server.

02 · verifiable, not promised

Trust comes from the code, not the marketing.

Each of the four guarantees below maps to a specific line in the open-source repository. Read it before trusting it; ignore the marketing.

01

Open source, every line

The entire production stack — frontend, gateway, ingest, reaper, receipt service, Postgres schema, Caddy config — is in one MIT-licensed repository. `docker compose up -d` brings it online on your own machine.

02

Audited primitives only

XChaCha20-Poly1305 + BLAKE2b via libsodium (independently audited multiple times since 2013). Per-recipient encryption via age (Filippo Valsorda's spec, NCC Group audit 2022) lands in v1.0. Zero hand-rolled cryptography.

03

EU jurisdiction by infrastructure

The production VM is hosted by Hetzner Online GmbH — wholly EU-incorporated, no US parent company, no US CLOUD Act exposure. The data centre is Falkenstein FSN1 in Germany. No CDN proxies the data path.

04

Tamper-evident audit chain

Every share creation, every chunk delivery, every burn fires a hash-linked entry in Postgres' audit_chain table. SHA-256 prev-hash binds each entry to the one before; tampering anywhere in the chain breaks verification. v1.0 publishes the Merkle root externally so the chain is verifiable without trusting our database.

03 · what we never see

The architecture prevents it — not the policy.

01

Your file's contents

Encrypted in your browser before any byte leaves your machine. The server only ever stores ciphertext. A subpoena returns ciphertext. A breach of the database returns ciphertext. There is no plaintext copy anywhere on our infrastructure.

02

Your decryption key

Generated by your browser's RNG, stored in the URL fragment (`#key=…`). Browsers refuse to send fragments to any server by design (RFC 3986 §3.5). The key never touches our network at any point.

03

Your recipient's identity

No signup, no account, no email collected for downloads. Anyone who receives the URL can decrypt; we have no way to map a download to a person. The shortId is the access secret in v0.1, full stop.

04 · what we do see

Operational honesty.

The flip side of the previous section. These are the things our servers do collect — minimal, scoped, and documented so a security-minded reader can decide if the trade-off fits their threat model.

01

Sender IP fragment (hashed)

Used purely for rate-limiting (10 share creates per minute, 100 per day, per IP). Stored as a SHA-256 hash, never as the raw IP. Discarded after 24 hours. The recipient's IP is not stored at all.

02

Operational metadata

Timestamps, share IDs, chunk counts, ciphertext sizes, expiry windows — the kind of things any web service logs to keep itself running. None of it reveals what a file contains or who downloaded it. Cleared on share destruction.

05 · verify it yourself

Don't take our word for it.

Five grep commands. Each one points at the source line that backs one of the trust claims above. Run them against a clone of the repo and check that the answer matches what we said.

# 1. The server cannot read your file — the encryption key is
#    generated in the browser via libsodium's randombytes_buf:
grep -rn "generateKey\|randombytes_buf" packages/crypto-core/src/

# 2. The key never reaches the server — it lives in
#    window.location.hash and travels in the URL fragment, which
#    browsers never send to servers:
grep -rn "window.location.hash\|#key=" apps/web/src/

# 3. The server only stores ciphertext — every blob written to MinIO
#    is the AEAD output, never plaintext:
grep -rn "PutObjectAsync" services/ingest/Services/

# 4. Per-chunk AAD binds (shareId, chunkIndex) so chunks can't be
#    silently reordered or moved between shares:
grep -rn "buildChunkAad" packages/crypto-core/src/

# 5. The audit chain is hash-linked entry-to-entry — tampering
#    breaks the chain and verify_audit_chain returns the seq of
#    the broken row:
grep -rn "append_audit_entry\|verify_audit_chain" db/migrations/

Want to run the entire stack on your own machine? docker compose up -d from the repo root spins up all 14 services on your localhost — same images, same configs, same Postgres schema as production.

06 · what's NOT here yet

Three milestones, one honest list.

v0.1.0-alpha

Symmetric MVP — what's live today

Browser-side XChaCha20-Poly1305 encryption, EU-hosted MinIO storage, server-driven burn-after-read, hash-linked Postgres audit chain. Suitable for portfolio review and personal experimentation; the SlothBox glue is not yet independently audited.

v0.5.0

Accounts and signed receipts

Lucia auth + magic-link, share dashboard with manual revoke, RFC 3161 timestamped delivery receipts, single-use HMAC chunk tokens that close the parallel-readers race in the burn-after-read flow.

v1.0.0

Production-grade

Per-recipient encryption via age sealed-boxes, verifiable burn-after-read with public Merkle root, offline `slothbox-verify` CLI distributed via brew/scoop/apt, independent cryptographer review + third-party application pen test published under /audits/. The “production-grade” wording is gated behind these.

The full per-version scope sits in MILESTONES.md in the repo. The threat model and explicit non-goals live on /security.

How it works · SlothBox