HB Updated Aug 29, 2026

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.toString on a patched getter won’t return [native code] unless you also patch toString — which is itself detectable from a worker thread, where your page-context patch never ran.
  • Object.getOwnPropertyDescriptor(navigator, 'plugins') looks different after defineProperty than before.
  • The Accept-Language HTTP header and navigator.languages are 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.”

Coherent vs incoherent identity

Coherence is not just intra-browser. The network layer participates too:

  • navigator.languages vs the Accept-Language header (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)

Camoufox architecture

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:

  1. 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.
  2. 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

https://github.com/Hunt-Benito/the-fingerprint-that-hangs-together-coherent-identity-spoofing-with-camoufox

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, pair curl_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

  1. Camoufox — anti-detect browser: https://github.com/daijro/camoufox
  2. Camoufox release v152.0.4-beta.29 (Aug 21, 2026): https://github.com/daijro/camoufox/releases/tag/v152.0.4-beta.29
  3. Camoufox Python interface: https://github.com/daijro/camoufox/tree/main/pythonlib
  4. BrowserForge — fingerprint generator: https://github.com/daijro/browserforge
  5. Juggler — Firefox automation protocol: https://github.com/puppeteer/juggler
  6. Playwright documentation: https://playwright.dev/python/
  7. bot.sannysoft.com — browser automation detection test: https://bot.sannysoft.com/
  8. Camoufox stealth overview (maintenance-gap warning): https://camoufox.com/stealth
  9. MaxMind GeoLite2: https://dev.maxmind.com/geoip/docs/databases/geoip2-and-geoip-lite2/
  10. Patchright — patched Playwright for Chromium: https://github.com/Kaliiiiiiiiii-Vinyzu/patchright
  11. HumanCursor: https://github.com/riflosnake/HumanCursor
  12. python-socks: https://github.com/romis2012/python-socks
  13. Hunt-Benito — The Evolution of Web Scraping: https://www.hunt-benito.com/blog/the-evolution-of-web-scraping-from-lwpsimple-to-headless-browsers/