The Fingerprint That Hangs Together: Coherent Identity Spoofing with Camoufox
Modern anti-bot systems rarely catch scrapers because a single signal lies. They catch them because the signals disagree — a Windows user agent sitting on top of an Apple GPU, a Tokyo timezone on a Texas IP, an en-US locale dialing in from Edinburgh. This article is a hands-on deep dive into fingerprint coherence: why naive spoofing collapses under cross-checking, how the freshly-revived Camoufox anti-detect browser attacks the problem at the Gecko engine level, and a fully working PoC that benchmarks detection, rotates coherent identities, and scrapes a JS-rendered site through a real 4G mobile exit.
Introduction
In our previous article on the evolution of web scraping, we traced the journey from Perl one-liners to headless-browser fleets, and closed with a Snapchat crawler that leaned on human-like behavior — variable delays, natural scrolling, session warm-up. That covers the behavioral half of modern bot detection. This article is about the other half, the one that kills most scrapers before they get to behave at all: the device fingerprint, and specifically whether it hangs together.
A browser session exposes thousands of measurable attributes: the user agent, the platform string, screen dimensions and pixel ratio, the WebGL vendor and renderer, installed fonts, supported codecs, CPU core count, touch points, timezone, Intl locale, navigator languages, AudioContext properties, and dozens more. Anti-bot vendors (Cloudflare, Akamai, DataDome, HUMAN, Kasada, Imperva) don’t score these values individually — they score the joint probability of seeing them together. A Windows 11 UA with an ANGLE (NVIDIA, GeForce GTX 980 Direct3D11) renderer is plausible. A macOS UA with that same Direct3D11 renderer is impossible. A session whose timezone is Europe/London but whose exit IP geolocates to São Paulo is a red flag on a plate.
The result is that randomization is the enemy. The scrapers that survive are not the ones that randomize each fingerprint value independently — they are the ones that draw a single coherent identity from the real-world distribution of devices, and then keep every layer consistent with it, including the network layer. That is a much harder engineering problem than it sounds, and it is exactly the problem Camoufox — a Firefox fork purpose-built for scraping and AI agents — is designed to solve. After a year-long maintenance gap, the project is back under active development, with three beta releases in July–August 2026 (the latest, v152.0.4-beta.29, shipped on August 21).
We’ll put those claims to the test with real runs: a detection benchmark, an identity-rotation demo, and a full scrape through a genuine carrier-grade mobile exit — including the cases where things fail, because those are the instructive ones.
Why Naive Spoofing Fails: The Cross-Check Problem
Three generations of evasion, and where each one breaks
| Approach | Example | How it breaks |
|---|---|---|
| HTTP header spoofing (2005–2012) | Set User-Agent to Chrome’s |
Header UA ≠ navigator.userAgent; TLS fingerprint (JA3/JA4) still says python-requests |
| JS property patching (2012–2020) | Object.defineProperty(navigator, 'webdriver', {get: () => undefined}) |
toString() no longer returns [native code]; descriptor mismatch between page and worker contexts; injected script itself is detectable |
| Real browser + stealth patches (2020–) | Playwright + stealth plugins, patched browsers | Automation protocol leaks (CDP), headless-mode artifacts, and — most commonly — incoherent fingerprint combinations |
The middle row deserves emphasis because it’s still the default approach baked into most open-source stealth libraries and copy-pasted from most tutorials — which makes it exactly the one anti-bot vendors know best. Any property you overwrite from JavaScript leaves fingerprints of its own:
Function.prototype.toStringon a patched getter won’t return[native code]unless you also patchtoString— which is itself detectable from a worker thread, where your page-context patch never ran.Object.getOwnPropertyDescriptor(navigator, 'plugins')looks different afterdefinePropertythan before.- The
Accept-LanguageHTTP header andnavigator.languagesare set by different code paths; patch one and not the other and the mismatch is trivially visible server-side.
The only spoofing that survives JavaScript introspection is spoofing that never touches JavaScript — intercepting the value where the browser engine produces it, in C++. That is Camoufox’s core design decision: it patches Firefox (Gecko) at the implementation level, so a spoofed navigator.platform is produced by the same native code path that would produce the real one. There is nothing for toString() to catch, because nothing was replaced.
The coherence invariant
Even with engine-level spoofing of every individual value, you can still lose. The values must be jointly plausible. Camoufox’s own documentation puts it plainly: “Camoufox can spoof fingerprints with a correct market share. However, fingerprints must also be internally consistent. A Windows user agent with an Apple M1 GPU, a MacOS user agent with a Windows DirectX renderer, and a mobile device with a desktop screen resolution are all impossible, and will be flagged for being suspicious.”
Coherence is not just intra-browser. The network layer participates too:
navigator.languagesvs theAccept-Languageheader (handled by Camoufox, which aligns them)- Timezone/locale vs exit-IP geolocation — the one that catches nearly everyone using proxies
- IP ASN class vs claimed identity — a “home user” fingerprint arriving from an AWS datacenter IP is self-refuting
Camoufox in 2026: What It Is, and an Honest Status Check
Camoufox (MPL-2.0, by the author of BrowserForge) is a stripped-down Firefox fork with fingerprint interception compiled into the engine. The pieces that matter for this article:
| Layer | What Camoufox does |
|---|---|
| Fingerprint injection | Navigator, screen, viewport, geolocation, timezone, locale/Intl, WebRTC IPs, voices, device counts — spoofed at the Gecko C++ level, no JS injection |
| Identity generation | Unset properties are auto-populated from BrowserForge, which samples from the real-world statistical distribution of devices (OS market share, GPU-per-OS frequencies, screen sizes) |
| Automation hiding | Playwright’s page agent runs sandboxed in an isolated copy of the page via a patched Juggler protocol — no window.__playwright__ artifacts; navigator.webdriver fixed; headless pointer-type leak fixed |
| GeoIP coherence | With a proxy, derives timezone, locale, and geolocation from the exit IP’s region (MaxMind GeoLite2), and picks browser language by the distribution of languages spoken there |
| Fonts | Ships Windows/macOS/Linux font sets and uses the correct set for the claimed OS; randomizes letter spacing to defeat font-metrics fingerprinting |
| Humanization | Optional human-like cursor movement (a C++ port of HumanCursor) |
Attention! An honest status check before you build on this. Camoufox’s own README carries a warning that we verified at the time of writing: after roughly a year of maintenance gap, the project states it “has gone down in performance due to the base Firefox version and newly discovered fingerprint inconsistencies” and is “currently under active development.” The beta cadence in July–August 2026 (beta.27 on July 16, beta.28 on July 19, beta.29 on August 21) is the recovery in progress — that freshness is why this article exists — but treat it as a beta: pin your version, test against your targets, and don’t assume yesterday’s bypass holds tomorrow. Two more documented limits worth knowing upfront:
- It’s a Firefox. Camoufox cannot fully impersonate Chromium fingerprints. Some WAFs probe for SpiderMonkey engine behavior directly (the project links a Cloudflare interstitial demo), which is impossible to spoof from inside a Firefox-derived engine.
- Coherence is a moving target. Anti-bot vendors repeatedly test Camoufox to find one inconsistency, then update their client-side scripts to check for it. The maintainers say so themselves.
We ran everything below against browser v152.0.4-beta.29 (build date August 20, 2026) with Python package camoufox 0.5.5 on Python 3.12. All outputs shown are real runs from this setup, not mockups.
The PoC: Benchmark, Coherence Probe, Rotation, and a Real Scrape
The PoC has one script with three modes — --benchmark (detection results plus a coherence probe that checks the fingerprint against the exit IP’s geography), --sessions N (identity rotation), and --scrape (a JS-rendered site with humanized behavior) — plus a baseline script for stock Playwright so the comparison is apples-to-apples.
Setup
$ python3 -m venv venv && source venv/bin/activate
$ pip install -U 'camoufox[geoip]'
$ python -m camoufox fetch # downloads the browser (~1.2 GB) + GeoLite2
$ python -m camoufox version
Python Packages
Camoufox v0.5.5
Browserforge v1.2.4
Playwright v1.60.0
Browser
Current browser v152.0.4-beta.29
Build date Aug 20
Baseline: stock Playwright Firefox, headless
$ python baseline_playwright.py
[*] Navigating to https://bot.sannysoft.com/
WebDriver FAILED
Plugins is of type PluginArray FAILED
[*] Summary: 21 passed, 2 failed, 1 not applicable
[*] navigator.webdriver = True
[*] UA = Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0
Two hard failures, and worse: the UA announces the scraper’s host OS (X11; Linux x86_64 — the actual machine), navigator.webdriver is true, and the browser is a Playwright-patched Firefox build, not a stock distribution — another signal fingerprinters use to separate automation fleets from real users. Any WAF scores this session dead on arrival.
Camoufox without a proxy: clean detection, but watch the coherence probe
$ python coherent_scrape.py --benchmark
[*] Navigating to https://bot.sannysoft.com/
Chrome failed (N/A on real Firefox)
[*] Summary: 23 passed, 0 failed, 1 not applicable (Chrome-only checks)
[identity]
user agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) ...
platform : Win32
languages : ['en-US', 'en']
screen : 3072x1728 (avail 3072x1680, dpr 1)
timezone : UTC
intl locale : en-GB
webgl : Google Inc. (NVIDIA) / ANGLE (NVIDIA, NVIDIA GeForce GTX
980 Direct3D11 vs_5_0 ps_5_0), or similar
[exit ip]
ip : xx.xx.xx.xx (redacted)
geolocation : London, England, GB
asn / org : AS5607 Sky UK Limited
ip timezone : Europe/London
[coherence verdict]
fingerprint timezone == ip timezone : FAIL
languages plausible for country : en-US,en vs GB
(We redacted the exact IP from our run — the direct-exit benchmark discloses the machine’s real address, so reproduce it on infrastructure you don’t mind fingerprinting.)
Every individual spoof is coherent now — Windows UA, Win32 platform, a plausible NVIDIA Direct3D11 renderer, matching en-US languages. The one flagged “failure” is the Chrome-object check, which fails on every genuine Firefox too; it is not evidence of automation. But look at the coherence verdict: timezone UTC on a London residential IP. The identity is internally consistent and still geo-incoherent, because with no proxy configured Camoufox left the timezone at its default. This is the exact class of mismatch WAFs cross-check — and it’s why the geoip option exists.
The proxy wrinkle nobody documents
We ran the coherent version through a real mobile proxy exit, and immediately hit a practical problem worth sharing because it will bite you too: Firefox cannot launch with an authenticated SOCKS5 proxy (Browser does not support socks5 proxy authentication), and neither Firefox nor Python’s HTTP stack speaks TLS-to-proxy for HTTPS proxy endpoints. The fix is a tiny local relay — an unauthenticated HTTP listener that forwards through the authenticated upstream. Our PoC repo includes one (relay.py, ~70 lines using python-socks):
$ python relay.py 8888 $PROXY_USER $PROXY_PASS proxy.example.com 1080 &
[relay] listening on 127.0.0.1:8888 -> socks5://proxy.example.com:1080
$ curl -x http://127.0.0.1:8888 https://ipinfo.io/json
{
"ip": "92.40.194.156",
"hostname": "92.40.194.156.threembb.co.uk",
"city": "Edinburgh", "region": "Scotland", "country": "GB",
"org": "AS206067 Hutchison 3G UK Limited",
"timezone": "Europe/London"
}
That hostname and ASN are the point of a mobile exit: this is a real carrier network (threembb.co.uk, AS206067 Hutchison 3G UK), indistinguishable at the network layer from a phone in Edinburgh.
Camoufox with geoip=True: the verdict flips
$ python coherent_scrape.py --benchmark --proxy 127.0.0.1:8888 --proxy-type http
[*] Proxy: http://127.0.0.1:8888 (geoip coherence enabled)
[*] Summary: 23 passed, 0 failed, 1 not applicable (Chrome-only checks)
[identity]
user agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) ...
platform : Win32
languages : ['en-GB', 'en']
timezone : Europe/London
webgl : Google Inc. (Intel) / ANGLE (Intel, Intel(R) HD Graphics
400 Direct3D11 vs_5_0 ps_5_0), or similar
[exit ip]
ip : 92.40.195.17
geolocation : Edinburgh, Scotland, GB
asn / org : AS206067 Hutchison 3G UK Limited
ip timezone : Europe/London
[coherence verdict]
fingerprint timezone == ip timezone : PASS
languages plausible for country : en-GB,en vs GB
With geoip=True, Camoufox probed the exit IP through the proxy, resolved it to Scotland, and rebuilt the identity around it: timezone Europe/London, languages switched from en-US to en-GB, and the locale picked by the actual distribution of languages in the region. The fingerprint now agrees with the network on every axis we can check. Note the exit IP also changed between connections (.194.156 → .195.17) — carrier NAT rotation, which we’ll come back to.
Identity rotation with coherence held fixed
Run three sessions and you can watch BrowserForge rotate the device while geoip pins the geography:
$ python coherent_scrape.py --sessions 3 --proxy 127.0.0.1:8888 --proxy-type http
[*] Spawning 3 fresh identities:
session 1: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) | screen 3072x1728 | tz Europe/London | Google Inc. (Intel)
session 2: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) | screen 2560x1440 | tz Europe/London | Google Inc. (NVIDIA)
session 3: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) | screen 1680x1050 | tz Europe/London | Google Inc. (Intel)
Screens, GPUs, and the rest of the fingerprint churn per session; the timezone stays pinned to the exit. That combination — device rotates, geography doesn’t — is what multi-session scraping actually needs, because a scraper fleet where every session lives in a different country is its own anomaly.
Scraping a JS-rendered site through the mobile exit
Finally, the extraction mode: paginated scraping of quotes.toscrape.com/js/ (a deliberately JS-rendered sandbox — nothing renders without script execution), with humanized scrolling between extractions, all traffic through the relay:
$ python coherent_scrape.py --scrape --proxy 127.0.0.1:8888 --proxy-type http --out quotes.jsonl
[*] Scraping https://quotes.toscrape.com/js/ (JS-rendered)
page 1: 10 quotes, 10 new (total 10)
page 2: 10 quotes, 10 new (total 20)
...
page 10: 10 quotes, 10 new (total 100)
[*] Done: 100 records in 56.2s -> quotes.jsonl
100 unique records across all 10 pages, deduplicated on extraction (our first draft had a pagination bug that silently re-read page 1 twenty times — always dedupe and assert page-level novelty in your crawlers; silent duplication is the most common way a “working” scraper is actually broken).
The IP Layer: Where the Fingerprint Meets the Network
Device coherence is half the problem; the exit IP is the other half, and the two are scored together. Anti-bot systems classify every session’s IP by ASN and reputation:
| Exit class | Typical trust | Coherence notes |
|---|---|---|
| Datacenter / cloud | Lowest | A “home user” fingerprint from an AWS range is self-refuting; fine for non-protected targets and API work |
| Residential | Medium-high | Real ISP customers; the default choice for most protected targets |
| Mobile / carrier-grade NAT | Highest | Thousands of real subscribers share each public IP; blocking one IP harms real users, so WAFs are structurally reluctant to hard-block them |
Mobile exits have a second property we observed live in the runs above: the public IP rotates between connections (.194.156 → .195.17, minutes apart) because that is simply how carrier networks behave for real phones. From the WAF’s perspective, your scraper sessions are scattered across a shared pool that real consumer traffic constantly uses.
Honesty corner: none of this means you always need mobile proxies. For static-content targets without bot protection, no proxy (or a cheap datacenter one) is the right answer, and for most protected but not paranoid sites, residential exits do the job. Mobile earns its premium in the worst cases — hardened e-commerce and travel targets, per-session rate limiting, geo-fenced content where the country must match exactly, and scraping mobile-app API endpoints where the server expects carrier IPs. That geoip-coherence pairing is also why we run SimplyProxies, our own UK mobile proxy service on self-owned 4G/5G devices — the benchmark you just read ran through it, and it is the same advice we give clients: match the exit class to the target’s paranoia, and let the fingerprint follow the IP, never the other way around.
Limits, Failures, and When to Use Something Else
A tool article that only shows passes is marketing, not engineering. Here is where this stack genuinely struggles:
- SpiderMonkey tells on you. Camoufox is Firefox-derived and cannot impersonate a Chromium engine. WAFs that probe JavaScript-engine behavior directly (the Camoufox project itself links a Cloudflare interstitial demo that does this) will identify the engine no matter what the UA claims. If your target’s protection probes engine internals, a Chromium-based approach (e.g. Patchright, a patched Playwright for Chromium) is the better fit.
- The project says it is not production-stable. Read the warning quoted above again: a year-long maintenance gap, known fingerprint inconsistencies, beta status. Pin versions; re-test after every upgrade.
- Behavioral detection is only partially addressed. The built-in humanized cursor helps, but the maintainers state it “may still be detected with sophisticated enough analysis.” Layer your own timing variance (see the Snapchat crawler article).
- TLS and HTTP/2 fingerprints are out of scope for a browser-context article — a real Firefox ships a real Firefox TLS stack, which is exactly why browsing through Camoufox is stronger than patching headers on
requests. But if you need raw HTTP speed at scale, paircurl_cffi-style TLS impersonation with everything you learned about coherence here. - CAPTCHAs are a separate economic problem (solve vs. avoid). The cheapest CAPTCHA is the one you never trigger — which is the entire point of not looking like a bot.
And the standing caveat that applies to every tool in this space: anti-bot vendors run the same open-source tools against their own detectors. Whatever bypasses exist today are being catalogued today.
Responsible Scraping
Everything above is engineering; whether to apply it, where, and at what rate is a judgment call. Our standing rules: respect ToS and robots where your jurisdiction and contract situation require it (the case law — eBay v. Bidder’s Edge, hiQ v. LinkedIn — is messier than blog posts claim), don’t break authentication boundaries, throttle to load levels a human browsing population would plausibly generate, and prefer licensed APIs and datasets when they exist. The techniques in this article are for targets where you have a legitimate data-collection right and the anti-bot layer is the only obstacle.
Sources
- Camoufox — anti-detect browser: https://github.com/daijro/camoufox
- Camoufox release v152.0.4-beta.29 (Aug 21, 2026): https://github.com/daijro/camoufox/releases/tag/v152.0.4-beta.29
- Camoufox Python interface: https://github.com/daijro/camoufox/tree/main/pythonlib
- BrowserForge — fingerprint generator: https://github.com/daijro/browserforge
- Juggler — Firefox automation protocol: https://github.com/puppeteer/juggler
- Playwright documentation: https://playwright.dev/python/
- bot.sannysoft.com — browser automation detection test: https://bot.sannysoft.com/
- Camoufox stealth overview (maintenance-gap warning): https://camoufox.com/stealth
- MaxMind GeoLite2: https://dev.maxmind.com/geoip/docs/databases/geoip2-and-geoip-lite2/
- Patchright — patched Playwright for Chromium: https://github.com/Kaliiiiiiiiii-Vinyzu/patchright
- HumanCursor: https://github.com/riflosnake/HumanCursor
- python-socks: https://github.com/romis2012/python-socks
- Hunt-Benito — The Evolution of Web Scraping: https://www.hunt-benito.com/blog/the-evolution-of-web-scraping-from-lwpsimple-to-headless-browsers/