🔐 Security architecture
Deliberate transparency. This page documents our defences
and their limits. No security through obscurity: a red team deserves to know what it is attacking. All code is licensed
AGPL-3.0. For the test framework, see the
rules of engagement; for demonstrations, the
Attack Simulator.
1. Authentication — SelfRecover no email
- No email, no phone number. On sign-up: you choose a recovery word → a 16-character
password and an EFF diceware passphrase are generated server-side. - Storage:
Argon2id(password), Argon2id(passphrase), Argon2id(derived_key) — m=64 MiB, t=4, p=2 (OWASP profile). No secret is ever stored in the clear. - Derivation bound to the hostname the browser reads:
HMAC-SHA256(key = memorised word, message = hostname ‖ "|v2" ‖ account salt) → a word phished on another address does not yield the right key. - Progressive rate limiting (5 failures / 15 min) plus a per-IP sign-up cap (anti-enumeration, anti-spam).
2. Data encryption — two models, by sensitivity
a) Server blind-key (profile: bio, location, link) — XChaCha20-Poly1305, key derived from a server secret held outside the database and outside the webroot. A SQL dump yields nothing but blobs.
b) Client-side end-to-end (personal memo) — encrypted in the browser (WebCrypto). Argon2id (64 MiB, t=3, p=1 — profile shared by SelfDataGuard and SelfRecover, defined in Primitives and kept in step by a conformance test) → HKDF per label → a random vault_key encrypts the memo, itself wrapped in two envelopes (password and recovery passphrase). The server holds no key.
✅ What this protects
- Blind-key: stolen disk, SQL dump, injection
- E2E: even admin or root access on the server leaves the memo unreadable
⛔ What it does not (acknowledged)
- Blind-key: an admin, or an RCE that reads the key, can decrypt profiles and private messages
- E2E: a persistently compromised server serving tampered JavaScript that captures the password at unlock — the "served code" problem
3. One key per use — SelfRecover, the memo, SelfDataGuard
- Each use derives its own key, through its own derivation. Access goes through SelfRecover: your browser computes an
HMAC-SHA256 fingerprint of your word, bound to the site name and salted per account; the server only keeps an Argon2id of it. The memo draws two child keys, separated by HKDF label: data-enc from your password, data-recover from your recovery passphrase. - Cardinal rule: never the same key for authentication and encryption. The server sees authentication; it must never be able to decrypt.
- Recovery: if your memo's recovery passphrase is also your SelfRecover one, a single secret restores access and the memo — through two separate derivations, with no shared key. Recovery strength comes from the entropy of the input (diceware passphrase), not from hash length.
4. Application hardening
- CSRF: session-bound HMAC token, verified on every write action (
X-CSRF-Token header). - Headers:
Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, HSTS. - Sessions: 192-bit random token,
HttpOnly/SameSite=Lax cookie, 24 h TTL with purge. - Anti-enumeration: non-discriminating error messages, per-IP limits.
5. Moderation — SelfModerate anti-manipulation
- Per-member reputation (starting at 20/30), ±1 votes on posts and on members, a reason required for a downvote.
- Anti-Sybil: an account under 2 minutes old with no contribution cannot vote.
- Pack: voters linked to each other (private messages both ways) hitting the same target → votes cancelled, reputation restored. Burst of unlinked votes → flagged to an arbiter, nothing cancelled.
- Anti-farming: repeated votes from one member to another are neutralised. Graduated sanctions — loss of voting rights, then a ban of 2 minutes → 10 minutes → 30 minutes; the last tier repeats, never permanent, and every ban is written to the moderation journal.
6. Threat model — stated honestly
✅ Mitigated
- Database exfiltration (dump, stolen disk) — blobs only
- Password brute-forcing (rate limiting)
- Moderation manipulation (Sybil, pack-voting)
- CSRF, clickjacking, passive session theft
- Memo theft, even with root access (E2E at rest)
⚠️ Known limits
- Profiles and private messages readable by whoever obtains the server key (
.blindkey), for instance through an RCE — not through the admin panel - Persistently compromised server → tampering with the served code
- Metadata is not encrypted (who talks to whom, and when)
7. Roadmap
E2E extended to private messages and profiles · an off-machine integrity supervisor: tampering with the served code and out-of-scope requests are already detected, but from the monitored machine itself — so persistent root access can silence the witness; an outside witness and reversible automatic containment remain to be built · distributed quorum (Shamir) for critical keys.