← Back to dashboard
anonymitytoolingfreshReader view (for NotebookLM)

Anonymity Integration Guide

What is anonymity, actually?

The real model

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.

You are 1 in
2750.0M
1.0 bits of identifying information
Indistinguishable — this is what an unmodified Tor Browser gives you.

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.

Text
 █████╗ ███╗   ██╗ ██████╗ ███╗   ██╗██╗   ██╗███╗   ███╗██╗████████╗██╗   ██╗
██╔══██╗████╗  ██║██╔═══██╗████╗  ██║╚██╗ ██╔╝████╗ ████║██║╚══██╔══╝╚██╗ ██╔╝
███████║██╔██╗ ██║██║   ██║██╔██╗ ██║ ╚████╔╝ ██╔████╔██║██║   ██║    ╚████╔╝ 
██╔══██║██║╚██╗██║██║   ██║██║╚██╗██║  ╚██╔╝  ██║╚██╔╝██║██║   ██║     ╚██╔╝  
██║  ██║██║ ╚████║╚██████╔╝██║ ╚████║   ██║   ██║ ╚═╝ ██║██║   ██║      ██║   
╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚═╝  ╚═══╝   ╚═╝   ╚═╝     ╚═╝╚═╝   ╚═╝      ╚═╝   

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:

PropertyQuestion it answersBroken by
ConfidentialityCan they read the content?Weak/absent E2EE, backdoored endpoints
PrivacyCan they link content to behaviour?Logging, tracking, data brokers
AnonymityCan 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

ResourceURL
Onion services (operator docs)https://community.torproject.org/onion-services/
Tor protocol specificationshttps://spec.torproject.org/
Tor Browser manualhttps://tb-manual.torproject.org/
Whonix documentationhttps://www.whonix.org/wiki/Documentation
SecureDrop (whistleblower intake)https://securedrop.org/
Arti (Rust Tor) API docshttps://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 saysReality in 2026
Install CanvasBlocker, User-Agent Switcher, Ghostery, AdblockNever 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/FlashControlChrome 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-sitePer-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 TorVPN+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 anonymityBitcoin 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.orgBrainwallets are catastrophically insecure and were mass-drained. Never generate key material in a web page.

Dead, renamed, or superseded

Book (2015)StatusModern equivalent
Torbutton, HTTPS EverywhereMerged into Tor Browser core; HTTPS Everywhere retired Jan 2023Built-in Security Level + HTTPS-Only Mode
v2 .onion addresses (16 char)Removed from Tor, Oct 2021v3 onions — 56 char, ed25519/SHA3
Shallot / Scallion (v2 vanity)Dead with v2mkp224o (v3 ed25519 vanity)
TextSecure + RedPhoneMerged into Signal (2015)Signal — PQXDH (2023) + SPQR "Triple Ratchet" (Oct 2025)
CryptoCat, Torchat, ChatSecureDiscontinued / unmaintainedSimpleX, Briar, Cwtch (metadata-resistant)
Tor Instant Messaging BundleCancelled — never shippedas above
TrueCryptDiscontinued 2014VeraCrypt, LUKS2, BitLocker, FileVault
TorBirdy, EnigmailDiscontinued 2020Thunderbird built-in OpenPGP
Freenet + Frost + FuqidRenamed Hyphanet (2023); front-ends abandonedHyphanet, I2P, Nym mixnet
MultiBit, MultiSigna, most listed exchangesDefunct—
DarkcoinRenamed Dash (2015); PrivateSend is opt-in CoinJoin, not anonymityMonero if privacy is the actual requirement
PanopticlickRenamedEFF Cover Your Tracks
SkypeRetired May 2025Signal
Macchanger as a manual stepMAC 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).

Bash
# 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 --socks5 resolves 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

Bash
npm install socks-proxy-agent

socks-proxy-agent implements Node's http.Agent, so it works with anything built on http/https — axios, node-fetch, got:

TypeScript
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 fetch silently ignores agent. Native fetch is undici, which only honours a dispatcher. Passing { agent } to it does not error — it just sends the request over your real IP. If you must use native fetch, build an undici Agent with a SOCKS connect function; otherwise stay on axios/node-fetch for proxied calls.

Fail closed, so a misconfiguration can never fall back to a direct connection:

TypeScript
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.

Bash
pip install stem pysocks requests[socks]
Python
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)

NEWNYM gives 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.

Bash
# /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 1
Bash
sudo systemctl reload tor
sudo cat /var/lib/tor/my_service/hostname   # -> <56-char>.onion

The 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/:

Bash
# /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:

Bash
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.

TypeScript
// 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.

TypeScript
/** 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:

TypeScript
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:

TypeScript
// 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.

TypeScript
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_key belongs in Hazina or an encrypted backup, never in the repo. Add hs_ed25519_secret_key and *.auth_private to .gitignore on any project that publishes one.
  • socks5h://, never socks5://. 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 254XXXXXXXXX number is linked to a SIM registration under Kenyan law, which is linked to an ID. In boda-dispatch, duka-order-bot, and clinic-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 CheckoutRequestID and 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


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.