WHITEPAPER.TXT β€” Notepad_β–‘X

TintedSand: A Self-Hosted, Minimal-Dependency Privacy Suite

Status: Draft v0.4 Scope: WireGuard-based VPN with a coordination broker and onion-style multi-hop routing across a dynamic set of hosts, plus a custom E2E-encrypted chat service, a custom E2E-encrypted file-sharing service, and a minimal custom mail stack. Single or multi-operator self-hosted deployment.


1. Abstract

This document describes the architecture and design rationale for a self-hosted privacy suite consisting of five pieces β€” a coordination broker, a mesh of WireGuard-based hosts supporting multi-hop routing, a custom chat service (with rooms/presence, Β§8.1), a custom volunteer-replicated file-sharing service (Β§9), and a minimal, receive-only temporary-mailbox service (Β§10) β€” built with two hard constraints: (1) minimize third-party dependencies and off-the-shelf "all-in-one" software, preferring small, well-understood building blocks or hand-rolled code where reasonable, and (2) protect traffic and communications metadata from ISPs and other network-level observers, including reducing the ability of any single host in the mesh to see both who a client is and what they're accessing. The system is meant to be run by an initial operator but designed so additional independent hosts can join the mesh over time.

2. Motivation

Commercial VPN/chat/email providers require trusting a third party with traffic logs, message content, or mailbox contents. A single-server self-hosted VPN improves on that but still means one host sees everything: who connected and what they accessed. This project adds a second improvement β€” spreading trust across multiple hosts via multi-hop circuits, coordinated by a lightweight broker β€” so that no single host in the mesh can see both the client's identity/origin and their traffic's destination, similar in spirit to how Tor separates entry, middle, and exit relays. It does this while keeping the dependency footprint small enough for a single operator to read and understand the whole system.

3. Design Principles

  1. Minimal dependencies. Prefer one well-audited library per cryptographic primitive over a framework. Prefer standard Linux daemons (Postfix, Dovecot) already in every distro's repos over bundled reimplementations. Avoid dependency chains deeper than necessary.
  2. Self-made where it matters. The chat protocol, the broker, and the multi-hop circuit logic are custom-built, since these are the layers where the operator wants full control of the protocol and trust model. The VPN transport uses WireGuard as-is (Β§6); the onion-style layering on top of WireGuard is self-made (Β§7).
  3. No unnecessary logging. No component persists connection metadata, IP-to-identity mappings, or message content beyond what's operationally required.
  4. Defense in depth over defense in obscurity. Security comes from correct use of standard cryptographic primitives, a hardened host, and a sound trust model β€” not from hiding how the system works.
  5. Auditable over featureful. If a feature requires a new dependency, the default answer is no.
  6. Honest about anonymity-set size. A multi-hop network's anonymity guarantee scales with how many independent, diverse hosts are actually relaying traffic at a given time. This is called out explicitly in Β§14 rather than implied by the architecture diagram.

4. Threat Model

In scope (what this protects against):

  • Passive ISP/network observation of a client's traffic via encrypted tunneling.
  • A single compromised or malicious mesh host learning both "who the client is" and "what they're accessing" β€” mitigated (not eliminated) by multi-hop circuits where entry and exit knowledge is split across hosts.
  • Opportunistic eavesdroppers on shared/untrusted networks.
  • Chat message content being readable by the chat server operator or anyone who compromises the server (end-to-end encryption).
  • Casual mailbox snooping by the mail host's infrastructure provider.

Out of scope (explicitly not defended against):

  • A global passive adversary who can observe traffic entering and exiting the whole mesh and correlate by timing/volume β€” this is the same limitation Tor has, and is much harder to solve than the mesh itself, especially with a small number of hosts (see Β§14).
  • Legal compulsion of any individual host operator.
  • A compromised client endpoint.
  • Nation-state-level exploitation of host OS/hypervisors.
  • Malicious hosts that join the mesh specifically to run entry+exit correlation attacks β€” mitigated by circuit construction policy (Β§7.3) but not eliminated, especially while the host pool is small.

5. System Architecture Overview

                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚        Broker          β”‚   (coordination only β€”
                     β”‚  - authenticates       β”‚    never sees client
                     β”‚    clients & hosts     β”‚    data-plane traffic)
                     β”‚  - issues credentials  β”‚
                     β”‚  - publishes signed    β”‚
                     β”‚    host directory      β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚ (control plane: TLS, mutual auth)
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                 β–Ό                 β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Host A    β”‚     β”‚  Host B    β”‚     β”‚  Host C    β”‚   ...dynamic,
        β”‚ (entry)    │────▢│ (middle)   │────▢│ (exit)     β”‚   hosts join/
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   leave via broker
              β–²
              β”‚ WireGuard tunnel (client ↔ entry host)
              β”‚ + onion-layered circuit (client ↔ entry ↔ middle ↔ exit)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Client    │──▢ fetches host directory + circuit assignment from broker
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     builds circuit, only entry host is directly reachable

Chat, file sharing, and mail services sit behind the mesh the same way a "hidden service" sits behind Tor: reachable only through an exit/rendezvous host, not directly on the public internet.

5.1 Client surfaces: web vs. native β€” one product, two access points

"The VPN project" is one product with two client surfaces, not a bundle of separate apps:

  • The website is the paid-access hub for everything except the VPN tunnel itself: account/payment, chat, file-share, and mail-account management all happen there, already gated by the same broker-issued token (Β§7.5). This is deliberate, not a placeholder β€” a browser tab is the right surface for account/chat/file-share/mail, and building a second native UI for the same thing would just be a second thing to keep in sync.
  • The native client (mesh-host/connect, and its mobile counterparts) is scoped to the VPN connection only β€” Mullvad-style: fetch the directory, pick/verify a host, bring up the tunnel. It is not meant to grow into a bundled chat/mail/file-share app. "More security features to come" (Β§15 Phase 9) means things that only make sense as a local, privileged process β€” kill switch (block all non-tunnel traffic if the tunnel drops), auto-reconnect, split-tunneling, DNS-leak protection β€” the same category of feature Mullvad's own app ships, not new product surface.
  • Dual mode, selectable: the native client supports both of this project's own VPN shapes β€” direct single-hop WireGuard (Β§6, fast, already self-serve via mesh-host/vpn.go's ?format=qr and the website's /connect page) and the anonymous multi-hop onion circuit (Β§7.2's streaming-circuit extension, slower but spreads trust across hops). These are a real speed-vs-anonymity tradeoff, not two skins on the same thing β€” the client should make the choice explicit, not hide it.
  • No custom app needed for the direct-VPN case at all: /connect hands a phone (Android or iOS, either) a QR code the official WireGuard app scans directly β€” same "point at the existing mature ecosystem" reasoning as everywhere else in this section. A custom native client only earns its keep for the onion-circuit mode and the Mullvad-style extras above, since neither exists in any off-the-shelf app.
  • This mirrors Β§10's mail decision (reuse the existing PGP/MUA ecosystem rather than build a client) and Β§6's VPN decision (reuse WireGuard's own transport rather than reimplement it): build custom software for the parts unique to this project (broker, mesh, chat/file-share protocols, onion-mode tunneling), reuse or point at existing tooling everywhere else.

6. Component: VPN Transport (WireGuard)

Unchanged from the single-host design: WireGuard is used as-is for the encrypted tunnel between adjacent hops (client↔entry, entry↔middle, middle↔exit), rather than reimplementing tunnel crypto. Rationale: ~4,000 lines of reviewed, formally-analyzed kernel code using modern fixed primitives (Curve25519, ChaCha20-Poly1305, BLAKE2s). This is the one deliberate exception to "self-made" β€” reimplementing transport crypto is the highest-risk possible place to cut corners.

What's self-made on top of it: key generation/provisioning scripts, per-hop config generation, firewall/NAT rules, and β€” new in this version β€” the circuit-construction and onion-layering logic in Β§7.

7. Component: Broker + Multi-Hop Mesh Routing

7.1 Broker role: coordination-only

The broker is intentionally kept out of the data plane:

  • Authenticates clients: issues short-lived credentials (signed tokens or client certs) after some enrollment step the operator controls (invite code, pre-shared key, or manual approval).
  • Authenticates hosts: each host that wants to join the mesh registers a long-term public key with the broker; the broker signs a directory entry for it. This is the "hosts can join and leave" mechanism β€” closer to a lightweight version of Tor's directory authorities than a full control plane.
  • Publishes a signed host directory: a periodically-refreshed, signed list of currently active hosts, their public keys, roles/capabilities (entry-capable, exit-capable, region, load), and reachability info. Clients fetch this directory to build circuits themselves β€” the broker doesn't choose the circuit for the client, it just supplies verified data for the client to choose from.
  • Never sees data-plane traffic. Once a client has the directory and has authenticated to its chosen entry host, all actual traffic flows clientβ†’entryβ†’middleβ†’exit without touching the broker again. This keeps the broker's blast radius small: compromising it lets an attacker issue bad credentials or a bad directory, but doesn't itself expose traffic content or a full circuit's endpoints unless combined with a compromised host.

7.2 Onion-style circuit construction

Rather than one flat WireGuard hop from client to a single exit, the client builds a layered circuit, minimum three hops (entry/middle/exit) unless the operator configures fewer for a smaller deployment:

  • The client encrypts its actual request in layers, one per hop, using each hop's public key (from the signed directory) β€” the same core idea as Tor's onion encryption, implemented here over a WireGuard-secured link per hop rather than TCP+TLS per hop.
  • Each hop can only decrypt its own layer, learning the next hop's address but not the original client's address (except the entry host, which necessarily knows the client's IP) nor the final destination (except the exit host, which necessarily knows the destination but not the client).
  • This is the concrete meaning of "different hosts route/defrag certain requests": each hop peels one layer and forwards, and the exit host reassembles/forwards the plaintext request to its destination (or to the chat/mail service if the request targets one of this project's own services).

Streaming circuits (persistent sessions, not just one-shot request/response). The original design above is a single round trip per circuit β€” right for relaying one chat message or file chunk, but not enough to carry general traffic (web browsing, DNS, anything long-lived), which a phone acting as a VPN needs. mesh-host/build.go's CircuitStream/OpenStream extend the same onion layering with a Stream flag: hops keep their connection to the next hop (or, at the exit, the destination) open across multiple frames instead of dialing fresh and closing after one round trip (relay.go's handleConn/forward/dialExit). Key material is still fresh per frame (never reuses ephemeral keys, same rule as one-shot circuits) β€” streaming reuses the connections, never the ECDH. Stated limitation: this is a ping-pong stream, not independent concurrent full-duplex β€” each Send both writes a chunk and polls the destination for whatever it's sent back within a short bounded window (readAvailable, 300ms). Fine for request-driven protocols; a destination that pushes data unprompted (no client write to piggyback the read on) needs a genuinely concurrent relay, which is real follow-up work, not implemented here. This is the backend prerequisite for Β§5.1's onion-circuit VPN mode β€” a client (e.g. Android's VpnService + a tun2socks-style bridge) still needs to be built on top of it.

7.3 Circuit selection policy

  • Circuits should mix hosts run by different operators/providers where possible (diversity constraint) β€” a circuit made of three hosts on the same VPS provider under the same operator provides little real protection against that operator, and the directory should expose enough metadata (ASN, provider, operator identity if disclosed) for clients to enforce a "no two hops from the same operator/ASN" rule.
  • Circuits rotate periodically (time-based, e.g. every 10 minutes, matching Tor's default-ish cadence) and per new "destination" to limit long-lived correlation.
  • Hosts should not be told they're "entry" vs "exit" a priori in a way that lets a host game its position β€” role eligibility is a directory attribute (has public inbound reachability = can be entry/exit; else middle-only) but the client's actual per-circuit choice should be randomized within eligible hosts.

7.4 Dynamic host membership

  • A host registers with the broker (proves control of a keypair, gets added to a pending list), the operator either auto-approves based on policy or manually approves new hosts (recommended early on, given how few hosts there likely are β€” see Β§12).
  • Hosts send periodic signed heartbeats to the broker; missed heartbeats drop them from the published directory (graceful leave) and clients rebuild circuits around active hosts only.
  • The directory itself is signed by the broker's key and versioned, so a host can't unilaterally insert itself without the broker's cooperation, and clients can detect a stale/tampered directory.

7.5 Free/paid tiering and entitlement

Every host self-reports a Tier ("free" or "paid") at registration, published in the directory alongside role/operator (MONETIZATION.md Β§0). This is capacity/priority segmentation only β€” free-tier hosts run the exact same onion-routing code, the same no-logs policy, and the same circuit diversity rules as paid-tier hosts; nothing about the privacy guarantee is different, only pool size.

Client credentials (ClientToken, Β§7.1) carry the same tier: a free token comes from the unauthenticated, capped POST /enroll/free path (shorter TTL, no invite/account needed); a paid token comes from consuming an invite minted via POST /admin/gen-invite, which the website's payment backend calls only after a confirmed payment (crypto via a self-hosted BTCPay Server, recommended, or card via Stripe). Because the token is self-contained and self-verifying (payload + Ed25519 signature, checkable offline against the broker's public key), the same credential doubles as the entitlement check for chat, mail, and file-share (Β§8-10) β€” those services never call back to the broker or a separate accounts database at connection time, they just verify the token they're handed.

8. Component: Chat Service (Custom-Built)

Unchanged in substance from the single-host design, but now reachable through the mesh (via an exit host acting as a rendezvous point) rather than only directly over one VPN's wg0.

  • Identity: X25519 keypair per client.
  • Message encryption: ChaCha20-Poly1305 (libsodium) via ECDH-derived keys, HKDF.
  • Server is a relay only β€” stores ciphertext by recipient public key until delivered, then deletes it (short TTL, e.g. 7 days, for offline delivery).
  • Transport: WebSocket over TLS, reachable only via the mesh (not exposed on any host's public interface directly).
  • Forward secrecy: Phase 8 adds a minimal hash-ratchet β€” each side derives a KDF chain (HKDF(chain_key) β†’ (next_chain_key, message_key)) so compromising one message's key doesn't expose earlier or later messages in the session, the same property Signal's Double Ratchet gives you, without adopting Signal's library or protocol. A periodic fresh X25519 exchange (reusing the ECDH primitive already in crypto.go, just re-run on a timer) adds a coarse DH-ratchet step on top for post-compromise recovery. This is deliberately the minimal version: no X3DH prekey bundles, no out-of-order message handling beyond what the existing offline-delivery queue already does β€” documented trade-off against full Signal/Matrix parity, not free.
  • Access: paid-tier only (Β§7.5). The WebSocket handshake requires a valid, unexpired broker-issued token; chat-server verifies it offline against the broker's public key, no callback needed. A free-tier token is rejected, same as no token at all.

8.1 Rooms and presence (Jabber/Matrix-like, still self-made)

Deliberate decision (see WHITEPAPER's client-direction discussion): get the properties people associate with XMPP/Matrix β€” multi-party rooms, presence β€” without adopting XMPP/Matrix server software itself (Prosody/ejabberd/Synapse/Dendrite), which would be the single largest dependency in this entire project and a direct contradiction of Β§3's "self-made where it matters" principle. Both additions build on structures the chat service already has, not new infrastructure:

  • Rooms, sender-side fan-out (Signal-groups-style, not Matrix's shared-room-key Megolm): a room is just a set of member pubkeys tracked server-side as opaque metadata (membership list only β€” the server still never sees plaintext). A message to a room is encrypted once per member by the sender (reusing the existing per-recipient ECDH+AEAD path unchanged) and the ciphertext relay fans it out to each member's individual queue exactly like a 1:1 message today. This avoids needing a shared group ratchet/key-distribution protocol (Matrix's Megolm) at the cost of doing one encryption per member client-side β€” the right tradeoff at the room sizes a self-hosted, small-mesh product actually has (dozens, not thousands, of members).
  • Presence: an ephemeral, in-memory-only "online since" flag per client identity, set on WebSocket connect and cleared on disconnect, visible to a client's own contacts/room co-members β€” no persistence, consistent with Β§3's no-unnecessary-logging principle (presence history is exactly the kind of metadata this project commits to not retaining).
  • Explicitly deferred, not attempted in v1: multi-device (Signal's own "linked devices" model is genuinely complex β€” per-device key state, sync fan-out β€” and not worth taking on until rooms/presence/ratchet are proven out first).

9. Component: File Sharing (Custom-Built, Volunteer-Replicated Storage)

A minimal, self-made E2E-encrypted file transfer service sitting alongside chat, reusing the same client identity (the X25519 keypair each client already generates for chat) rather than inventing a second identity system.

  • Encryption: sender encrypts the file client-side before it ever leaves the device β€” same ECDH (X25519) + HKDF + ChaCha20-Poly1305 primitives as chat, just applied to file bytes instead of a single message. Storage nodes never have plaintext or key material, matching the chat trust model exactly.
  • Chunking, not streaming media transcoding or anything fancier: files are split into fixed-size chunks (e.g. 4MB) client-side, each chunk independently encrypted and authenticated, so transfer is resumable (a dropped connection only costs the in-flight chunk, not the whole file) without needing a general-purpose streaming framework.
  • Storage model: full replication across volunteer storage-role mesh hosts, not a single server. A mesh-host can advertise an optional storage capability at registration (the same shape as the existing vpn capability in Β§7.5/the VPN host list) alongside a capacity figure. When a client uploads a transfer, it picks N (e.g. 3) storage-capable hosts from the signed directory and replicates each encrypted chunk to all of them, rather than one relay server holding the only copy. This removes the single-host-storage bottleneck and spreads trust the same way the mesh already spreads circuit trust β€” no single volunteer holding a full, un-deletable copy of anyone's data.
    • Why replication, not erasure coding or BitTorrent-style swarming: all three give equivalent confidentiality, since chunks are already client-side ciphertext before any storage node sees them β€” a node holding a full replica learns nothing an node holding one-Kth of an erasure-coded shard wouldn't also learn (i.e., nothing). The real anonymity variable is metadata (who talks to which storage node, when), which is solved by mesh-routing (below), identically regardless of storage strategy. Given that, full replication is the version that needs no new dependency (no Reed-Solomon library, no erasure-coding math) and is the simplest to audit β€” erasure coding buys storage efficiency and better churn-resilience, not more anonymity, so it's deferred rather than adopted by default (revisit if storage costs at scale justify it). Literal BitTorrent-style swarming was considered and rejected outright: bare peer-to-peer piece exchange leaks participant IPs to other swarm members by design (the reason "torrent over a VPN" is its own common practice), so it would need every peer connection tunneled through the mesh to be safe at all β€” strictly more complexity than storage-role replication for no anonymity gain.
  • Reachability: storage nodes are only ever reached through the mesh, the same "hidden service" model as chat/mail (Β§5) β€” a client's upload/download requests to a storage-role host go through an onion circuit (Β§7.2), so no storage node ever sees the real uploading/downloading client's IP directly, matching the existing entry/middle/exit trust split rather than adding a new exception to it.
  • Sharing: a transfer can be marked shareable at upload time β€” rather than deleting chunks after the first successful download (today's one-shot-relay behavior), a shareable transfer's chunks stay replicated until TTL expiry so a manifest (transfer ID + per-chunk keys, itself only ever handed to the intended recipient(s) out-of-band, e.g. via chat) can be redeemed by more than one recipient. This is still relay-and-expire, not general-purpose file sync/versioning β€” no read receipts beyond delivery, no edit history.
  • Reward: volunteering the storage capability feeds the same contribution-credit mechanism already tracked via broker heartbeats/uptime (MONETIZATION.md Β§2.2/Β§5.5) β€” a storage node's uptime and (self-reported, later audited) capacity offered count toward hosting-cost credit the same way relay uptime already does, rather than inventing a second reward system for storage specifically.
  • Explicitly not attempted in v1: direct peer-to-peer transfer bypassing the mesh (defeats the anonymity property above), erasure-coded sharding (see above), and general file sync/versioning.
  • Access: paid-tier only, same broker-issued token as chat (Β§7.5) β€” file-share reuses chat's identity and its entitlement check, not a second gate.

10. Component: Mail Service (Temporary, Receive-Only Disposable Mailboxes)

Superseded from earlier drafts (persistent paid mailboxes with PGP-between-own-users, WKD key publishing) β€” see Β§10.1 for why. The shipped mail feature is a disposable, receive-only inbox: generate a random address, receive whatever lands in it for a short TTL, read it via the website, then it's gone. No outbound sending capability exists on this host at all β€” not "discouraged," not "rate-limited," structurally absent.

  • Why receive-only: a mail server's hardest, least code-fixable problem is sender reputation β€” IP/domain blocklisting, major providers silently dropping mail from unknown senders, none of which any amount of correct Postfix/DKIM config actually solves (dns-records.md Β§7, WHITEPAPER Β§14 already called this out). A host that never originates outbound mail has no sending reputation to damage in the first place β€” this isn't a mitigation, it's removing the entire problem class. It also fits the disposable-address use case better than a persistent mailbox would: the point of a temp address is "give this to a signup form instead of your real email," which only ever needs to receive a verification link, never send.
  • Reuses the existing Postfix/Dovecot/admind stack, reconfigured, not rebuilt: Postfix keeps accepting inbound MX delivery over port 25 exactly as before; the submission (587) and smtps (465) listeners are disabled entirely on this host, so there is no SASL login and nothing to authenticate against β€” mail/policyd's entitlement/rate-limit checks (built for gating outbound submission) simply have nothing left to guard and are not run in this deployment. Dovecot's virtual-mailbox model (shared vmail user, flat passwd-file) already fits "not a real persistent identity" perfectly, it's just now used for short-lived random addresses instead of long-lived paid ones.
  • admind gains a POST /tempmail/create (alongside its existing /provision//deprovision, same package): generates a random local-part, creates a short-TTL Dovecot mailbox entry, and returns the full address immediately β€” no password, since nothing ever needs durable IMAP login for a 10-minute-to-a-few-hours-lived inbox. Reading it is a website page (poll admind or the website's own read endpoint), not an IMAP client setup.
  • Expiry deletes content, unlike paid-mailbox deprovisioning: the existing /deprovision deliberately keeps the Maildir (Β§10 old text, still true for any future persistent-mailbox use). A temp mailbox is the opposite β€” retaining a disposable inbox's contents past its stated lifetime defeats the entire point and is a real no-logs violation, so expiry is a hard delete of both the passwd-file entry and the Maildir, on a short background sweep.
  • DKIM/OpenDKIM is no longer needed on this host β€” DKIM signs outbound mail to prove it's really from you; a host that never sends outbound has nothing to sign. dns-records.md's SPF/DMARC records stay published anyway, set to explicitly deny sending (v=spf1 -all, v=DMARC1; p=reject) β€” not because we need them for our own delivery, but so nobody can spoof mail as this domain and have it look legitimate to a receiving provider.
  • Access: free-tier eligible, not paid-gated β€” cheap to run (short TTL, no persistent storage, no outbound sending cost at all) and directly reinforces MARKETING.md Β§3's "free tier has the same privacy properties, just capped capacity" claim. Capped by a rate limit on /tempmail/create (same shape as /enroll/free's abuse mitigation, Β§7.5), not an account requirement.
  • Reachability: through the mesh via an exit/rendezvous host, same "hidden service" model as chat/file-share (Β§5) β€” reading a temp inbox shouldn't reveal the reading client's IP to the mail host any more than reading chat messages does.

10.1 What was shelved, and why

The original design (persistent paid mailboxes, real domain, full SPF/DKIM/DMARC posture, later extended with PGP-via-WKD for E2E content) is still fully built and unit-tested (mail/policyd, mail/admind, Postfix/Dovecot/OpenDKIM templates) β€” nothing was deleted, and the code is a valid starting point if a persistent-mailbox product is revisited later. It's shelved rather than pursued to a real domain right now because the reputation/deliverability problem it exists to solve doesn't have a code solution, and the temp-mailbox use case (anonymous signups) is both a better fit for this project's actual anonymity pitch and structurally immune to that whole problem. If persistent mailboxes come back later, they'd sit alongside temp mail as a separate, explicitly paid-and-reputation-managed offering, not a replacement for it.

11. Deployment & Provisioning

Two-layer installer:

  1. Bash bootstrap script (fresh Ubuntu/Debian host): host hardening (nftables default-deny, SSH hardening, unattended-upgrades, fail2ban, sysctl hardening), then hands off to Docker.
  2. Docker Compose stack: one container per local service. A host running in "mesh member" mode additionally runs the circuit-relay daemon and registers with the broker using its own generated keypair. A host running in "broker" mode runs only the broker service (lightweight β€” no data-plane component at all).

Both container and bare-metal (systemd unit) install paths are generated from the same config templates, matching the earlier decision to support both without maintaining two divergent codebases.

12. Security Hardening Checklist (host-level)

  • Firewall default-deny inbound; explicit allow only for the required WireGuard/relay ports, SSH, and mail ports if enabled.
  • SSH key-only auth, root login disabled, fail2ban on the SSH jail.
  • Kernel: IP forwarding restricted to what the relay role needs; sysctl hardening (rp_filter, disabled ICMP redirects, etc.).
  • Automatic security updates for the base OS.
  • Each service runs as its own unprivileged user; containers rootless where feasible.
  • No swap, or encrypted swap.
  • Full-disk encryption recommended for every host in the mesh, not just the operator's own β€” a compromised unencrypted disk on any relay host is a bigger problem now that hosts may be run by third parties.
  • Broker's signing key treated as the most sensitive secret in the whole system β€” compromising it lets an attacker publish a malicious directory to every client. Recommend offline/HSM-backed signing key with only short-lived delegate keys online, once the project matures past v1.

13. Dependency Audit

Layer Dependency Why it's kept Why it's not hand-rolled
VPN transport WireGuard (kernel/wg-tools) Core per-hop transport Reimplementing tunnel crypto is the highest-risk place to cut corners
VPN transport nftables Standard Linux firewall It's the OS
Mesh/broker libsodium / golang.org/x/crypto (X25519, ChaCha20-Poly1305, Ed25519 for directory signing) Onion-layer crypto + directory signatures Hand-rolled crypto primitives are a well-known source of catastrophic bugs
Mesh/broker Go std lib Broker + relay daemon runtime Avoids npm/pip dependency sprawl β€” see STACK.md
Chat libsodium / golang.org/x/crypto AEAD + ECDH Same reasoning as above
File sharing libsodium / golang.org/x/crypto Same AEAD + ECDH primitives, reused rather than a new crypto dependency Same reasoning as above
File sharing (storage) None β€” reuses mesh-host's existing directory/registration code Volunteer storage nodes are just mesh-hosts with a storage capability flag, same pattern as the vpn capability Full replication of already-encrypted chunks needs no erasure-coding library or DHT/tracker code
Mail (temp inbox) Postfix, Dovecot Standardized, decades-hardened protocols; inbound-only (submission/smtps disabled, Β§10) Reimplementing SMTP/IMAP breaks interop with the rest of the email world for no benefit
Mail (temp inbox) certbot ACME client Cert issuance protocol, not worth hand-rolling
Mail OpenDKIM β€” not deployed DKIM signs outbound mail; a receive-only host has no outbound mail to sign (Β§10) N/A β€” dropped, not hand-rolled
Mail PGP/OpenPGP β€” shelved with persistent mailboxes (Β§10.1) N/A N/A
Deploy Docker/Compose (optional path) Isolation, easy teardown Bare-metal path provided as an alternative

Explicitly avoided: web admin control panels, ORMs/databases for routing/chat/file-share/mail metadata (flat files/signed directory documents instead), message queues/brokers, Rspamd's ML spam stack, Mailcow/Mail-in-a-Box, Signal's full Double Ratchet library, a PGP/OpenPGP implementation of our own, erasure-coding/Reed-Solomon libraries for storage, STUN/TURN/ICE for file transfer, and β€” notably β€” no adoption of Tor/Arti itself, even though it does exactly this kind of onion routing, because the goal here is a self-made circuit/broker implementation the operator fully understands. This is a real trade-off against the honesty commitment in Β§3: Tor represents over a decade of adversarial research and bug-fixing that a new implementation does not inherit for free (detailed next, in Β§14).

14. Limitations & Non-Goals

  • Anonymity-set size is the central caveat of this whole design. Tor's anonymity guarantees depend on thousands of independently-operated relays worldwide; a "larger/dynamic" mesh that might realistically have single-digit-to-low-double-digit hosts in its early life provides meaningfully weaker protection against entry/exit correlation than Tor does, even with correct circuit-construction logic. This should be communicated to users plainly, not implied away by the onion-routing architecture diagram. Treat this as "distributed trust across a handful of hosts you or your community control," not "Tor-equivalent anonymity," until/unless the host pool grows substantially and stays diverse.
  • Novel protocol risk. A hand-built onion-circuit implementation has not had the ~20 years of adversarial research Tor's design has absorbed (timing attacks, guard discovery attacks, path selection attacks, directory attacks). Expect to find and fix real security bugs post-launch; do not market this as equivalent to Tor's anonymity guarantees.
  • Broker compromise blast radius: while the broker never touches data-plane traffic, a compromised broker signing key can redirect clients to attacker-controlled hosts. Key handling (Β§12) matters as much as the circuit crypto.
  • Mail deliverability depends on factors outside this project's control (IP reputation, blocklists).
  • No redundancy/HA for the broker itself in v1 β€” it's a single coordination point of failure for joining the mesh (existing circuits keep working without it, but new clients/hosts can't onboard if it's down). Consider this a Phase-2-or-later hardening target (e.g. multiple broker replicas sharing a signing key via threshold signatures), not a v1 requirement.

15. Roadmap

  • Phase 0 (this document): Architecture sign-off.
  • Phase 1: WireGuard provisioning scripts + host hardening bootstrap script (single host, no mesh yet).
  • Phase 2: Broker service (auth, signed directory, host registration/heartbeat) + a single-hop client↔host connection using the broker for discovery only, no onion layering yet.
  • Phase 3: Multi-hop onion circuit construction and layered encryption across entry/middle/exit; circuit rotation and diversity policy.
  • Phase 4: Custom chat server + minimal web client, reachable via the mesh.
  • Phase 5: File-sharing service (chunked E2E-encrypted transfer, reusing chat's client identity and transport), reachable via the mesh.
  • Phase 6: Temporary, receive-only disposable-mailbox stack (Postfix/Dovecot, no OpenDKIM), reachable via the mesh; DNS setup docs. (Persistent paid mailboxes were built first, then shelved in favor of this β€” Β§10.1.)
  • Phase 7: Docker Compose packaging + bare-metal installer path for all of the above.
  • Phase 8: Chat ratchet upgrade, broker key-handling hardening (offline/delegate signing keys), circuit diversity enforcement using ASN/provider metadata, optional WireGuard obfuscation transport for censored networks.
  • Phase 9 (Service Unification): Route chat, file-share, and temp-mail traffic through Phase 3's mesh circuits instead of connecting to those servers directly (closes the open item carried since Phases 4-6). Add the storage mesh-host capability and volunteer-replicated file-share storage (Β§9). Add rooms/presence to chat (Β§8.1). Extend mesh-host/connect with the Mullvad-style connection-manager features described in Β§5.1 (kill switch, auto-reconnect, split-tunneling, DNS-leak protection) rather than building a second bundled client app.

16. Conclusion

The guiding trade-off of this project is auditability and distributed trust over convenience and turnkey anonymity guarantees: every dependency pulled in has to justify itself against an operator reading the whole codebase in a weekend, and every anonymity claim has to be qualified by the actual size and diversity of the host pool at any given time. WireGuard and libsodium are the two deliberate exceptions where "self-made" correctly loses to "don't write your own crypto primitives" β€” the broker, the circuit-construction logic, the chat protocol, the file-sharing protocol, and the mail stack assembly are all built specifically for this project rather than adopted as a bundled product, with the explicit understanding that a homegrown onion-routing layer starts out weaker than Tor's and should be represented that way until real-world hardening and a larger host pool close that gap.