Anonymity Integration Guide
What is anonymity, actually?
Anonymity is a system property you engineer subtractively — the identifier you never collect is the one you never have to defend.
In practice most deanonymization happens in your own logs, not on the wire: a full IP in Sentry, a phone number in a URL, an EXIF GPS tag on an upload, or a submission timestamp that reconstructs the sender. Redact at the boundary (logger transport, Sentry beforeSend), truncate IPs to /24 and /48, strip upload metadata server-side, and set a retention job for every behavioural table. If you route egress through Tor, use socks5h:// — socks5:// resolves DNS locally and leaks every hostname while appearing to work. For codeAmani's Kenya-targeted builds this is sharp-edged: a 254XXXXXXXXX number is SIM-registration-linked to a national ID, so it is identity, not a contact string.
Six primitives that decide whether a system is anonymous
They compose in one direction only — a leak at any layer defeats every layer beneath it.
Why “hardening” your browser makes you easier to find
Anonymity is a crowd property. Every trait that makes you distinguishable shrinks the crowd you hide in. Toggle the “privacy” tweaks a 2015 guide would tell you to install.
Bit values are indicative figures from EFF’s Panopticlick / Cover Your Tracks research, not a measurement of your actual browser. The lesson is the direction, not the decimal: install Tor Browser and change nothing.
█████╗ ███╗ ██╗ ██████╗ ███╗ ██╗██╗ ██╗███╗ ███╗██╗████████╗██╗ ██╗
██╔══██╗████╗ ██║██╔═══██╗████╗ ██║╚██╗ ██╔╝████╗ ████║██║╚══██╔══╝╚██╗ ██╔╝
███████║██╔██╗ ██║██║ ██║██╔██╗ ██║ ╚████╔╝ ██╔████╔██║██║ ██║ ╚████╔╝
██╔══██║██║╚██╗██║██║ ██║██║╚██╗██║ ╚██╔╝ ██║╚██╔╝██║██║ ██║ ╚██╔╝
██║ ██║██║ ╚████║╚██████╔╝██║ ╚████║ ██║ ██║ ╚═╝ ██║██║ ██║ ██║
╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ Anonymity Integration Guide
Focus: Building systems that don't deanonymize the people who use them — threat modelling, metadata minimization, Tor/onion-service integration, and anonymous intake — plus a tool-by-tool audit correcting a decade of stale advice.
Overview
Anonymity is not a tool. It is a property of a system, measured against a named adversary. "Am I anonymous?" is an unanswerable question. "Can a passive observer of my ISP link this request to my legal identity?" is answerable, testable, and engineerable.
Three distinct properties get conflated constantly, and conflating them is the single most common failure in this space:
| Property | Question it answers | Broken by |
|---|---|---|
| Confidentiality | Can they read the content? | Weak/absent E2EE, backdoored endpoints |
| Privacy | Can they link content to behaviour? | Logging, tracking, data brokers |
| Anonymity | Can they link behaviour to an identity? | Metadata, correlation, one careless reuse |
Encryption gives you the first. It gives you neither of the other two. A perfectly encrypted message that arrives from your home IP, at a predictable hour, to a recipient only you contact, is not anonymous — and this is precisely how most real deanonymizations happen.
For codeAmani the practical surface is defensive: we build products used by riders, clinic patients, SACCO members, donors, and whistleblowers. Our job is to avoid collecting the identifiers that would deanonymize them, not to help anyone evade lawful process.
The last box is the one that matters. Anonymity is a crowd property. You are only as anonymous as the number of people who look identical to you. Every "optimization" that makes you unusual — a rare font set, a custom user-agent, a niche browser extension — shrinks your crowd and hurts you. This inverts most people's intuition and is the reason the audit below rejects so much traditional "hardening" advice.
Official Documentation
| Resource | URL |
|---|---|
| Onion services (operator docs) | https://community.torproject.org/onion-services/ |
| Tor protocol specifications | https://spec.torproject.org/ |
| Tor Browser manual | https://tb-manual.torproject.org/ |
| Whonix documentation | https://www.whonix.org/wiki/Documentation |
| SecureDrop (whistleblower intake) | https://securedrop.org/ |
| Arti (Rust Tor) API docs | https://docs.rs/arti-client |
| Privacy Guides (tool selection) | https://www.privacyguides.org/ |
The 2015 → 2026 audit
This guide was commissioned as an audit of Tor and the Dark Art of Anonymity (Lance Henderson, 2015 edition). The book's principles aged well; its tooling did not, and a meaningful fraction of its advice is now actively harmful.
Full chapter-by-chapter mapping lives in reference/book-audit-2015-to-2026.md. Headlines:
Actively harmful if followed today
| Book says | Reality in 2026 |
|---|---|
| Install CanvasBlocker, User-Agent Switcher, Ghostery, Adblock | Never add extensions to Tor Browser. Each one raises fingerprint entropy and shrinks your anonymity set. Tor Browser ships letterboxing and a uniform fingerprint by design — customizing it is self-defeating. |
| Use Chrome with ScriptNo/ScriptSafe/FlashControl | Chrome is not an anonymity browser and never was. Flash reached EOL Dec 2020. Use Tor Browser, or Mullvad Browser (Tor Browser's fingerprint without the Tor network) for VPN use. |
| Manually configure NoScript per-site | Per-site whitelists are themselves a fingerprint. Use the built-in Security Level slider (Standard / Safer / Safest) and nothing else. |
| Pay for a VPN anonymously, chain it with Tor | VPN+Tor generally does not improve anonymity and often harms it. Use Tor's bridges + pluggable transports for censorship, not a VPN hop. |
| Use Bitcoin mixers (BitFog et al.) for anonymity | Bitcoin is pseudonymous, not anonymous; chain analysis is a mature industry. Mixing now carries severe legal exposure — Samourai Wallet's founders pleaded guilty and were sentenced to 5 and 4 years in late 2025, even as OFAC delisted Tornado Cash in March 2025. |
| Generate keys at Brainwallet.org | Brainwallets are catastrophically insecure and were mass-drained. Never generate key material in a web page. |
Dead, renamed, or superseded
| Book (2015) | Status | Modern equivalent |
|---|---|---|
| Torbutton, HTTPS Everywhere | Merged into Tor Browser core; HTTPS Everywhere retired Jan 2023 | Built-in Security Level + HTTPS-Only Mode |
| v2 .onion addresses (16 char) | Removed from Tor, Oct 2021 | v3 onions — 56 char, ed25519/SHA3 |
| Shallot / Scallion (v2 vanity) | Dead with v2 | mkp224o (v3 ed25519 vanity) |
| TextSecure + RedPhone | Merged into Signal (2015) | Signal — PQXDH (2023) + SPQR "Triple Ratchet" (Oct 2025) |
| CryptoCat, Torchat, ChatSecure | Discontinued / unmaintained | SimpleX, Briar, Cwtch (metadata-resistant) |
| Tor Instant Messaging Bundle | Cancelled — never shipped | as above |
| TrueCrypt | Discontinued 2014 | VeraCrypt, LUKS2, BitLocker, FileVault |
| TorBirdy, Enigmail | Discontinued 2020 | Thunderbird built-in OpenPGP |
| Freenet + Frost + Fuqid | Renamed Hyphanet (2023); front-ends abandoned | Hyphanet, I2P, Nym mixnet |
| MultiBit, MultiSigna, most listed exchanges | Defunct | — |
| Darkcoin | Renamed Dash (2015); PrivateSend is opt-in CoinJoin, not anonymity | Monero if privacy is the actual requirement |
| Panopticlick | Renamed | EFF Cover Your Tracks |
| Skype | Retired May 2025 | Signal |
| Macchanger as a manual step | MAC randomization is now default in iOS 14+, Android 10+, Windows 10+, NetworkManager | (nothing to do) |
Missing entirely from the book — and now central
Qubes OS (compartmentalization by virtualization; Qubes-Whonix is the strongest widely-available desktop posture) · GrapheneOS (hardened Android) · Snowflake / WebTunnel pluggable transports · Arti (Rust Tor, 2.0.0, embeddable as a library) · Post-quantum crypto (NIST FIPS 203/204/205, Aug 2024) · Commercial spyware (Pegasus, Predator, Graphite) as the realistic high-end threat, answered by iOS Lockdown Mode / Android Advanced Protection · Data brokers and SDK-based location resale, which deanonymize at scale far more cheaply than any SIGINT program · SecureDrop / Hush Line for anonymous intake.
Also notable: Tails merged into the Tor Project in September 2024 — they are now one organization.
Deliberately not modernized
The book's chapters on darknet-market escrow, "finalize early" tactics, evading law enforcement, and running hidden marketplaces are out of scope and intentionally left un-updated. They are operational crime guidance, not privacy engineering, and much of the surrounding ecosystem they describe (Silk Road 2.0, Agora, Blackbank, Sheep) was seized or exit-scammed within a year of publication. This guide covers the defensive and civil-liberties surface only: protecting users, journalists, and at-risk people from surveillance.
What the book got right, and still is
Worth preserving explicitly, because it's the durable part:
- You are the weak link. Nearly all real deanonymization is operational error, not broken cryptography.
- Compartmentalize identities absolutely. One reused username, one shared email, one crossed session collapses the whole construction.
- Correlation is the attack. Volunteering location, weather, local events, or timing patterns deanonymizes more people than exploits do.
- A stated threat model must precede tool selection. The book's instinct to ask "how far will you fall if caught?" before choosing tools is exactly right.
- Backdoors are security holes in 100% of cases. Still the correct position, and still contested — see the UK's Investigatory Powers Act notice that led Apple to pull Advanced Data Protection from the UK in Feb 2025.
Setup
Running a local Tor client
Everything below assumes a Tor SOCKS5 proxy on 127.0.0.1:9050 (daemon) or 9150 (Tor Browser).
# macOS
brew install tor && brew services start tor
# Debian / Ubuntu
sudo apt install tor && sudo systemctl enable --now tor
# Verify: should report Congratulations
curl -s --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip
--socks5-hostname(not--socks5) is load-bearing: it sends DNS resolution through the proxy. Plain--socks5resolves DNS locally and leaks every hostname you visit to your resolver — a total anonymity failure that still looks like it's working.
Node — routing requests through Tor
npm install socks-proxy-agentsocks-proxy-agent implements Node's http.Agent, so it works with anything built on http/https — axios, node-fetch, got:
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";
// socks5h:// = resolve DNS at the proxy. socks5:// leaks DNS locally.
const agent = new SocksProxyAgent("socks5h://127.0.0.1:9050");
const { data } = await axios.get("https://check.torproject.org/api/ip", {
httpAgent: agent,
httpsAgent: agent,
});
console.log(data); // { IsTor: true, IP: "..." }Node's native
fetchsilently ignoresagent. Nativefetchis undici, which only honours adispatcher. Passing{ agent }to it does not error — it just sends the request over your real IP. If you must use nativefetch, build an undiciAgentwith a SOCKSconnectfunction; otherwise stay on axios/node-fetch for proxied calls.
Fail closed, so a misconfiguration can never fall back to a direct connection:
export async function assertTor(agent: SocksProxyAgent): Promise<void> {
const { data } = await axios.get("https://check.torproject.org/api/ip", {
httpAgent: agent, httpsAgent: agent, timeout: 15_000,
});
if (!data.IsTor) throw new Error("Refusing to proceed: traffic is not over Tor");
}Python — controller access with Stem
stem is the Tor Project's own controller library — use it to build circuits, rotate identity, and publish onion services programmatically.
pip install stem pysocks requests[socks]import requests
from stem import Signal
from stem.control import Controller
# socks5h:// -> DNS resolved by Tor, not locally
PROXIES = {"http": "socks5h://127.0.0.1:9050",
"https": "socks5h://127.0.0.1:9050"}
print(requests.get("https://check.torproject.org/api/ip", proxies=PROXIES).json())
# Request a fresh circuit (rate-limited by Tor to roughly one per 10s)
with Controller.from_port(port=9051) as c:
c.authenticate() # cookie auth by default
c.signal(Signal.NEWNYM)
NEWNYMgives you a new circuit, not a new identity. Cookies, local storage, a logged-in session, and browser fingerprint all survive it. Treat it as changing your exit IP and nothing more.
Publishing a v3 onion service
Onion services give both parties anonymity and provide authenticated, end-to-end encrypted transport with no CA involved — the .onion address is the public key.
# /etc/tor/torrc
HiddenServiceDir /var/lib/tor/my_service/
HiddenServicePort 80 127.0.0.1:8080
# Enable the proof-of-work DoS defense (Tor 0.4.8+, 2023)
HiddenServicePoWDefensesEnabled 1sudo systemctl reload tor
sudo cat /var/lib/tor/my_service/hostname # -> <56-char>.onionThe directory now holds hs_ed25519_secret_key, hs_ed25519_public_key, and hostname. hs_ed25519_secret_key is the identity — anyone who copies it can impersonate the service permanently. Back it up encrypted; never commit it; mode 0600, owned by the tor user.
Restrict access to named clients (formerly "client authorization", now restricted discovery) by dropping public keys into authorized_clients/:
# /var/lib/tor/my_service/authorized_clients/alice.auth
descriptor:x25519:<BASE32_PUBKEY>Bind the backend to loopback only (127.0.0.1:8080). An onion service whose origin is also reachable on a public IP is trivially correlated and defeats the entire construction — this is how several high-profile services were located.
Vanity addresses
Shallot and Scallion from the book only ever produced v2 addresses and are dead. The v3 tool is mkp224o:
git clone https://github.com/cathugger/mkp224o && cd mkp224o
./autogen.sh && ./configure && make
./mkp224o -d ./out amani # addresses beginning "amani"Difficulty is exponential in prefix length — 6 characters is quick, 8 is hours, and beyond that is a research budget. A vanity prefix is branding, not security: users must still verify the full 56-character address.
Key patterns
Don't blanket-block Tor exit nodes
The most common way a product harms at-risk users is invisible: a WAF rule or a fraud vendor silently blocking every Tor exit. That locks out journalists, abuse survivors, and people under censorship — the exact users who need you most.
// Tier by ACTION, not by network origin.
// Tor traffic is not fraud; it is traffic whose origin you cannot see.
export function riskTier(req: Request): "open" | "challenge" | "deny" {
if (isReadOnly(req)) return "open"; // never block reading
if (isAccountMutation(req)) return "challenge"; // proof-of-work / captcha
return "deny"; // only for known-abusive patterns
}Rate-limit on a session or workload token, never on IP alone — IP-based limits punish everyone behind one exit node and are trivially evaded by anyone who matters.
Log hygiene — the leak that survives every other control
Most deanonymization risk in a normal SaaS lives in logs, not in the network.
/** Truncate IPs before they are ever written. IPv4 -> /24, IPv6 -> /48. */
export function coarseIp(ip: string): string {
if (ip.includes(":")) return ip.split(":").slice(0, 3).join(":") + "::/48";
return ip.split(".").slice(0, 3).join(".") + ".0/24";
}
const PII = /(\+?254\d{9})|([\w.+-]+@[\w-]+\.[\w.]+)|(\b[A-Z]{2}\d{6}\b)/g;
export const scrub = (s: string) => s.replace(PII, "[redacted]");Apply it at the boundary — Sentry beforeSend, the logger transport, and analytics — so no code path can bypass it:
Sentry.init({
dsn: process.env.SENTRY_DSN,
sendDefaultPii: false,
beforeSend(event) {
if (event.user) { delete event.user.ip_address; delete event.user.email; }
return JSON.parse(scrub(JSON.stringify(event)));
},
});Timing and volume are identifiers
An "anonymous" report submitted at 14:03 from a clinic that has four staff is not anonymous. Where the anonymity set is small, add jitter and batching rather than delivering immediately:
// Release anonymous submissions on a fixed cadence so submission time
// carries no information about event time.
const BATCH_WINDOW_MS = 60 * 60 * 1000;
export const releaseAt = (t: number) =>
Math.ceil(t / BATCH_WINDOW_MS) * BATCH_WINDOW_MS;Strip file metadata on upload
Photos carry GPS coordinates, device serials, and timestamps. For any user-supplied image, re-encode server-side and drop all EXIF — never trust the client to have done it.
import sharp from "sharp";
// Re-encoding drops EXIF/GPS by default; `rotate()` first so orientation
// survives the metadata loss.
export const sanitize = (buf: Buffer) =>
sharp(buf).rotate().toFormat("webp", { quality: 82 }).toBuffer();Anonymous intake
For genuine whistleblower or abuse-report intake, do not roll your own. Use SecureDrop (onion-based, hardened, designed for newsrooms) or Hush Line for a lighter-weight tip line. A bespoke "anonymous form" on your main domain shares TLS fingerprints, CDN logs, and analytics with the rest of your product, and almost always leaks.
codeAmani notes
Security
- Secrets stay server-side. Anonymity tooling changes nothing about this — see [[hazina-vault]] for the vault model. Never ship a key to the client because "the traffic is over Tor."
- Onion service private keys are identity.
hs_ed25519_secret_keybelongs in Hazina or an encrypted backup, never in the repo. Addhs_ed25519_secret_keyand*.auth_privateto.gitignoreon any project that publishes one. socks5h://, neversocks5://. The one-character difference is the difference between anonymized and fully leaked DNS. Grep for it in review.- Keep verifying webhook signatures (Stripe, Svix, M-Pesa) exactly as before — anonymity work never relaxes an authentication control.
African-market and Kenya-targeted projects
This is where the guide earns its keep, because our Kenya-targeted builds collect precisely the identifiers that deanonymize:
- Phone numbers are national identity. A
254XXXXXXXXXnumber is linked to a SIM registration under Kenyan law, which is linked to an ID. Inboda-dispatch,duka-order-bot, andclinic-salon-booking, the phone number is the identity — treat it as such. Hash it for joins, store it once, and never put it in logs, URLs, or analytics events. - GPS traces are re-identifying even when "anonymized." A rider's home and first pickup of the day identify them uniquely within days. Truncate stored coordinates to the precision the feature actually needs, and expire raw traces aggressively.
- M-Pesa
CheckoutRequestIDand till numbers are strong linkers. Keep them out of client-side state and error reports. - KDPA 2019 requires data minimization and purpose limitation, and gives data subjects erasure rights — a schema that scatters phone numbers across five tables makes compliance expensive. Design for deletion on day one.
- Low-bandwidth reality reinforces the right answer. Third-party trackers and heavy SDKs are both a privacy liability and a 2G/3G performance problem. Shipping zero third-party scripts is the rare choice that is simultaneously faster, cheaper, and more private.
AI routing
- Route anonymity/threat-model reasoning to Anthropic Claude (this guide's own audit was produced that way).
- Never send user PII to any model provider to "anonymize" it — redact deterministically with regex/NER before the call, as in
scrub()above. A model is not a redaction control; it is a network egress. - Self-host via [[hugging-face]] or run open weights through [[together-ai]] when the input is sensitive enough that a third-party API call is itself the leak.
Where this guide fits
- Coding-agent blueprint:
examples/AGENTS.md— portable rules for Claude Code, Grok Build, and Cursor, with per-tool adapters alongside it. - Full book audit:
reference/book-audit-2015-to-2026.md - Threat-model worksheet:
reference/threat-model-worksheet.md - Recipes:
PATTERNS.md· Links:RESOURCES.md· Gotchas:NOTES.md
Scope statement. This guide covers defensive privacy engineering, censorship circumvention, and protection of at-risk users — journalists, abuse survivors, whistleblowers, and people under repressive governments. It deliberately excludes operational guidance for evading lawful investigation, and does not modernize the source book's darknet-marketplace chapters.