← Back to blog

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

headless-browserbot-detectionpuppeteerplaywrightanti-detection
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.

Termagotchi
_

Ryan Underdown

Autodidact. Rarely listens to advice.

Follow on X @catamarammed or GitHub @underdown