Tor Mirror Audit: Is the .onion Swap Site the Same Site?
Eight paths fetched twice, over Tor and clearnet: the API matched byte for byte, HTML differed by exactly 150 bytes, five security headers vanished and one of two trackers still fires.
A promise you can measure. Every privacy-first swap site that publishes a .onion mirror makes the same implicit claim: it is the same site. Marketing pages cannot be audited, but this claim can. On 7 November 2026, between 06:14 and 06:19 UTC, I requested eight paths twice — once through Tor's SOCKS5 port with name resolution left to the proxy (`--socks5-hostname`, so the onion name never reaches a local resolver), once straight to the clearnet domain — and compared status, bytes, headers and latency. (Is Tor safe for swaps?)
Same backend, down to the byte. The JSON endpoints are identical on both sides: `/api/currencies` returned 71,655 bytes over each route, a 1,000 USDT-TRC20 quote came back at 181 bytes on both, an unknown order UUID produced the same 21-byte 404, `robots.txt` measured 525 bytes and `sitemap.xml` 448,943 bytes on each side. Nothing in that set suggests a second deployment or a stale copy. The mirror is the same Next.js origin, published a second way — which is exactly what a mirror should be, and also why header differences matter so much more than content differences. (XMR to USDT exit)
The 150 bytes, and where they come from. Each of the three HTML pages I compared over the onion was exactly 150 bytes smaller: the home page 72,531 against 72,681, `/exchange` 44,775 against 44,925, a blog post 64,617 against 64,767. The first divergence sits at byte 49,874 of the home page, and it is the contact link. Clearnet ships `<a href="/cdn-cgi/l/email-protection#0e7d…">` plus `/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js`; the onion ships a plain `mailto:[email protected]`. That is Cloudflare's email obfuscation, and it accounts for the whole difference. The mirror, in other words, is the un-rewritten origin: the more honest of the two copies.
Five headers disappear. Clearnet answers HTTP/2 with `strict-transport-security: max-age=31536000; includeSubDomains; preload`, `x-frame-options: SAMEORIGIN`, `x-content-type-options: nosniff`, `referrer-policy: strict-origin-when-cross-origin`, `permissions-policy: camera=(), microphone=(), geolocation=()`, plus `nel`, `report-to` and `cf-ray`. The onion answers HTTP/1.1 with `content-type`, `vary`, `date` and `connection` — and nothing else. No frame protection means the page can be iframed. No nosniff means a response with a mistyped content type can be sniffed into script by an old browser. The referrer policy reverts to the browser default, so a click from a page to a third party can carry the full URL rather than just the origin. HSTS is genuinely pointless on an onion — port 443 refused the SOCKS5 connection outright, so there is no TLS to pin — but the other four are not.
Caching, protocol, and what the origin really sends. Clearnet HTML carries `cache-control: public, max-age=0, s-maxage=300, stale-while-revalidate=900`; the onion served that line on cached hits and `private, no-cache, no-store` on cold ones, which is what a Next.js origin does with no CDN in front. On `/api/*` the asymmetry is cleaner: clearnet says `no-store`, the onion sends no cache-control at all. Clearnet also advertises `alt-svc: h3` — HTTP/3 — while the mirror speaks HTTP/1.1 only. None of this breaks a swap; all of it shows that the two copies are produced by different layers, and only one of those layers is configured.
One tracker is switched off; the other is not. The Clarity loader in the page source contains an explicit guard: if the hostname ends in `.onion`, it returns before creating the script tag. The UStat tag — `spst2.com/ustat.js`, loaded `afterInteractive` — has no such guard, and its tag appears verbatim in the onion HTML. I inspected what the page ships and did not execute the script, so this is a statement about the tag, not about the payload it would send. The asymmetry is still the finding: "the onion has no analytics" is true of one script out of two, and the difference between the two is one line of code.
What Tor costs, in seconds. Five home-page requests over the onion returned time-to-first-byte of 1.81–1.90 s (median 1.86) against 0.053–0.070 s on clearnet; three quote calls took 2.49–6.03 s against 0.21–0.58 s. The 448 KB sitemap needed 4.50 s end to end versus 1.34 s. Compressed, the home page is 16.6 KB and complete in 1.99 s — the payload is not the problem, the circuit is. For an order this is irrelevant: a quote stays valid for about 120 seconds and the deposit window is 30 minutes, so a three-second page load changes nothing. For browsing fifteen pairs before choosing one, it is the difference between a tool and a chore. (Is Tor safe for swaps?)
The thing the clearnet side is missing. Tor Browser looks for an `Onion-Location` header and offers a one-click redirect when it finds one. The clearnet response does not carry it, so a visitor already on Tor has to be told the address by someone else. The mirror itself is well behaved: it keeps `<link rel="canonical" href="https://veilswap.io">`, so it is not indexed as a duplicate, and `robots.txt` / `sitemap.xml` are the same 525 and 448,943 bytes. Correct for search, but it means discoverability depends on the address being published somewhere a user can find it. (Is Tor safe for swaps?)
Seven checks for any mirror, and the answers here. (1) Compare one API body byte for byte. (2) Diff one HTML page and explain every differing byte. (3) Diff the headers, especially nosniff, frame and referrer. (4) Grep the shipped HTML for analytics hosts. (5) Check whether TLS exists at all. (6) Take five TTFB samples, not one. (7) Look for `Onion-Location` on the clearnet side. On this site the results are: same backend, 150 bytes of email rewriting, five missing headers, one of two trackers skipped, HTTP/1.1 only, and about 1.8 seconds of extra latency. As of writing (7 November 2026, 06:1x UTC), 100 USDT-TRC20 quoted 0.14333064 XMR and 1,000 quoted 1.612097 XMR across two routes (148 bps advantage), the reverse leg 1 XMR quoted 607.322 USDT, the catalog held 438 assets on 65 networks with 52 stablecoins and six assets needing an extra ID. Those numbers move; the live quote governs. (XMR to USDT exit)