We Added a Tor Hidden Service.
Here's Why That Matters.
FaceVault is now reachable as a .onion hidden service. Not as a gimmick. Not as a checkbox. Because if you're building identity verification for the privacy-conscious, the least you can do is let them reach you without exposing their IP address.
A Confession
I've been fascinated by privacy network protocols since I first read the Tor design paper. Not the dark markets. Not the mystique. The engineering.
The idea that you can build a system where no single node in the network knows both who you are and what you're doing — that's not just clever cryptography. That's an architectural statement about what information should exist in the first place. It's the difference between "we promise not to look" and "we built it so looking is impossible."
I remember the first time I traced a circuit through three relays on a whiteboard. Entry node knows your IP but not your destination. Exit node knows the destination but not your IP. Middle relay knows neither. Each layer of encryption peels away at each hop — like an onion. And I thought: this is what infrastructure should look like. Not policy. Not terms of service. Infrastructure that makes surveillance architecturally impossible.
When we started building FaceVault, adding a hidden service wasn't a question of "should we." It was a question of "when can we."
The Beauty of Onion Routing
Most people think of Tor as a way to hide. That's underselling it. Tor is a transport layer protocol that separates identity from activity. The same way TCP/IP separates addressing from content, Tor separates who from what. It's layered abstraction. It's good engineering.
A Tor circuit
No exit node. Traffic never leaves the Tor network. End-to-end encrypted between your Tor client and our server.
A hidden service is even more elegant than normal Tor usage. There's no exit node — traffic never leaves the Tor network at all. Both the client and the server establish circuits to a rendezvous point, and the connection happens entirely within the overlay network. Neither side knows the other's IP address. Neither side can know.
The v3 onion protocol (the current one) uses ED25519 keys and SHA-3. The address itself is a hash of the service's public key — 56 characters of self-authenticating identity. No certificate authority needed. No DNS. No HTTPS. The address is the authentication. You can't MITM it because the address is cryptographically bound to the key. The protocol is the trust model.
.onion address bypasses all of them. The cryptography is the routing. The address is the certificate. That's not just privacy — it's a fundamentally stronger security model.
Every time I explain this to someone, I get a little excited. I can't help it. It's beautiful engineering. And it's been running in production, at scale, for over twenty years. Quietly protecting journalists, activists, whistleblowers, and anyone who needs to communicate without being watched.
The KYC Paradox
Here's the tension that most people see immediately: "You're a KYC company. You verify identities. Why would you offer a service designed to hide identities?"
It's a fair question, and the answer is simple: verifying your identity and protecting your network identity are two completely different things.
Proves you are who you claim to be
Face matches document. Document is legitimate. Person is real, not a deepfake. This is the verification.
Protects how you reach the verification service
Your IP address, your ISP, your geographic location, your browsing pattern. None of this is needed to verify your identity. None of it should be collected.
A bank needs to know you're you. A bank does not need to know which coffee shop's WiFi you were on when you submitted your passport photo. A gambling operator needs to verify your age. They don't need to log the IP address of the VPN you routed through to reach their signup page.
KYC is about who. Network privacy is about how you got here. Conflating the two is a design failure. We refuse to make that mistake.
How We Built It
The implementation is deliberately minimal. We compiled Tor from source in a multi-stage Docker build, with --disable-module-relay because we're not running a relay — just a hidden service. The final image is a minimal Linux base with only the Tor binary and its required libraries. No extras.
SocksPort 0
HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 127.0.0.1:80
HiddenServiceVersion 3
Four lines. No SOCKS proxy. No relay. No exit. Pure hidden service. Port 80 on the .onion routes to our web server, which serves the marketing site, the KYC webapp, and proxies API calls — all on a single origin. No TLS needed because Tor provides end-to-end encryption. No external domains loaded because everything is self-hosted.
http://*.onion
/ → Marketing site (Astro static)
/app → KYC webapp (camera, liveness, upload)
/api/v1/* → Verification API (FastAPI)
Headers:
Referrer-Policy: no-referrer
X-Real-IP: 127.0.0.1
CSP: default-src 'self' X-Real-IP: 127.0.0.1 — we hardcode this on every request through the .onion. Not because Tor would leak your IP (it can't), but because we don't even want the possibility of logging something meaningful in that header field. Defense in depth isn't just for anti-spoofing pipelines.
The CSP is self only. No Cloudflare analytics. No Telegram SDK. No Google Fonts. No external anything. Every byte served to a Tor user comes from our server and nowhere else. If you're going to offer a hidden service, you do it properly or you don't do it at all.
What Works on .onion
Everything. The full verification flow runs on the hidden service.
Camera access — .onion is a secure context in Tor Browser. getUserMedia() works. Liveness detection works. Head-turn challenge works.
File picker fallback — if camera is denied (Tor Browser's "Safest" setting or user preference), the file upload path works for ID documents.
Face detection models — MediaPipe WASM runs locally in the browser. No external model CDN. Self-hosted at /models.
API calls — same-origin at /api/v1. No CORS. No cross-domain requests. The webapp detects .onion and automatically uses the same-origin API base.
Anti-spoofing pipeline — rPPG, depth estimation, GAN texture analysis, all run server-side. The .onion transport is invisible to the ML pipeline.
<noscript> message explaining this.
Why Now
Three things converged.
First, we kept seeing verification attempts fail because the user was behind a proxy or VPN that our infrastructure flagged. Privacy-conscious users — the exact demographic that would trust a privacy-first KYC provider — couldn't reach us without jumping through hoops. That's backwards.
Second, the regulatory landscape is shifting. GDPR, POPIA, and their equivalents worldwide are making it clear that network metadata is personal data. IP addresses are PII. Collecting them when you don't need them isn't just bad practice — it's increasingly a legal liability. A hidden service eliminates the problem at the protocol level.
Third — and honestly, this is the real reason — it was time. We built the anti-spoofing pipeline. We built the document fraud detection. We stripped EXIF metadata from uploads. We hardcoded 127.0.0.1 as the client IP on every .onion request. Every layer of the stack was already designed around the principle that we collect only what we need and nothing more. The hidden service was the last missing piece. The protocol-level guarantee that even the connection itself reveals nothing.
"Privacy is not about having something to hide. Privacy is about having the right to choose what you share. A KYC verification proves you are who you say you are. It should not require you to also prove where you are, what network you're on, or what browser you're using."
If you're reading this on Tor Browser, you'll see a purple .onion available pill in the URL bar — that's the Onion-Location header at work. Click it. Run a verification. It works. The full pipeline, end to end, over onion routing. No compromises.
Try It
Open Tor Browser. Set security level to "Safer". Navigate to:
http://facevaij7po23zrx7hajx2caaorj4iqzhfxvvoo6ujxvyxq2yrwcy7yd.onion
This is a temporary address (6-character vanity prefix). We're generating the full facevault-prefixed address and will update when it's ready. The cryptographic identity changes, but the commitment doesn't.
References & Further Reading
Tor Rendezvous Specification v3 — the hidden service protocol specification
Tor Project: Onion Services Overview — how .onion addresses work
Protonmail's Tor Hidden Service — precedent for privacy services on Tor
Building Privacy-First KYC: Why We Delete Your Face — our data retention philosophy
Deepfake Defense: An IDS/IPS for Identity Verification — the anti-spoofing pipeline that runs on .onion