Performance.now() Is the Universal Headless Tell - 6 Browsers Tested

We're building detect-bot, a multi-layer headless browser detection system. After implementing 18 detection signals across two phases - browser surface checks and environment consistency cross-referencing - we ran every headless browser we could find against the same test harness to see what gets through.
None did. The best score across six different browsers was 65 out of 100.
Here's how each browser performed.
| Browser | Engine | Score | Verdict |
|---|---|---|---|
| Vanilla Playwright | Chromium CDP | 0 | BOT |
| Stealth Playwright | Chromium CDP + patch | 0 | BOT |
| Full Stealth Playwright | Chromium CDP + full fake | 0 | BOT |
| CloakBrowser v0.3.31 | Chromium 146 + 57 patches | 30 | SUSPICIOUS |
| Camoufox v0.4.11 | Firefox C++ fork | 35 | SUSPICIOUS |
| invisible-playwright | Firefox 150 C++ fork | 35 | SUSPICIOUS |
| Browserbase | Commercial CDP | 65 | SUSPICIOUS |
What Caught Each Browser
The three Playwright variants failed immediately on Phase 1 surface checks. Vanilla Playwright exposes navigator.webdriver = true - trivially flagged. Stealth patches that flag to undefined but leaves the plugin array empty, the window.chrome object missing, and performance.now() returning integers. Full Stealth fakes plugins, chrome, and hardware concurrency - but exposes a SwiftShader GPU renderer on a macOS user agent, which Phase 2 cross-referencing caught as physically impossible.
The anti-detection browsers - CloakBrowser, Camoufox, and invisible-playwright - all patch at the C++ level. They spoof plugins, WebGL renderers, hardware concurrency, and user agent strings. invisible-playwright even spoofs the timezone to America/Chicago, which beats the trivial UTC check but would fail IP-geolocation correlation at the server level.
But every single one of them fails on performance.now() precision.
| Signal | Vanilla | Stealth | Full Stlth | Cloak | Camoufox | invis | BB |
|---|---|---|---|---|---|---|---|
| navigator.webdriver | true | patched | false | false | false | false | false |
| plugins.length | 0 | 0 | 5 | 5 | 5 | 5 | 5 |
| window.chrome | missing | missing | shallow | 3 keys | N/A | 3 keys | 3 keys |
| chrome.runtime | missing | missing | missing | missing | N/A | missing | missing |
| Notification.permission | denied | denied | denied | denied | denied | denied | default |
| performance.now() | integer | integer | integer | integer | integer | integer | integer |
| Stack trace | eval | eval | eval | eval | clean | clean | eval |
| GPU renderer | SwiftShader | SwiftShader | SwiftShader | RTX 4060 | spoofed | spoofed | Intel |
| Timezone vs Locale | UTC+en-US | UTC+en-US | UTC+en-US | UTC+en-US | UTC+en-US | pass | UTC+en-US |
The Universal Tell
Real browsers have sub-millisecond timing precision. Open Chrome on your machine, open the console, and type performance.now(). You'll see something like 413527.41289000535 - 11+ decimal places. Headless browsers, across every engine, return integers: 413527.0, 413604.0, 413670.0.
This is an OS-level limitation. C++ source patches can spoof plugins, GPUs, timezones, and hardware concurrency. They cannot add sub-millisecond precision to the system clock. Every anti-detection browser we tested - Camoufox (Firefox fork with C++ patches), CloakBrowser (Chromium 146 with 57 source patches), and invisible-playwright (Firefox 150 fork with per-session unique fingerprints) - all returned integer-only values.
This single check drops a 25-point penalty in our scoring. Combined with Notification.permission = "denied" (15 points, 5 of 6 browsers) and single-language entries (15 points, 4 of 6 browsers), the anti-detection browsers land in the 30-35 range - marked SUSPICIOUS even when they pass every other check.
The Three Browsers That Bypass the Most
Camoufox and invisible-playwright tied at 35/100. Both are Firefox C++ forks. Camoufox patches the WebDriver flag, plugins array, GPU renderer, and languages. invisible-playwright adds timezone spoofing and generates per-session unique fingerprints from real Firefox telemetry data. Neither can escape the timing precision issue.
CloakBrowser scored lower at 30/100 despite being the most actively maintained tool - 57 source patches on Chromium 146. Its stack traces contain eval origins, which Camoufox and invisible-playwright avoid.
Browserbase, a commercial CDP browser service, scored the highest at 65/100. It spoofs the GPU to Intel, passes Notification checks, and has clean plugins. Two signals catch it: eval origins in stack traces and a missing chrome.runtime property.
What This Means for Bot Detection
The detection landscape has split into two tiers. Static property checks - navigator.webdriver, plugin arrays, window.chrome - are trivially patched by every anti-detection browser. The C++ forks spoof these at the source level. Checking them individually catches only vanilla automation.
What catches the C++ forks is signal combination. No single check produces a decisive result. performance.now() drops 25 points. Notification.permission drops 15. Single-language entries drop another 15. A timezone mismatch adds 20. The browser that patches all of these does not exist yet.
The next phase of detect-bot moves from static checks to behavioral analysis. The FP-Agent paper from May 2026 showed that Cloudflare Turnstile detected 1 of 7 AI agents using static fingerprinting - but behavioral analysis caught all 7 with near-perfect accuracy. Mouse movement patterns, scrolling behavior, and interaction timing are harder to spoof than property values.