Beyond User-Agents: Detecting Headless Chrome Through Window Geometry Physics
How Chromium's HeadlessModeBrowserWindow leaks synthetic non-client frame insets across Windows, macOS, and Linux Ozone environments.
- ✔ Modern bot detection ignores User-Agents and tests window chrome geometry physics.
- ✔ HeadlessModeBrowserWindow synthesizes mock frame insets mathematically.
- ✔ Windows synthetic frames leak 18px width and 96px height differences.
- ✔ macOS synthetic frames leak 26px width and 100px height differences.
- ✔ Default unconfigured runs trap instances in 800x600 virtual dimensions.
Beyond User-Agents: Detecting Headless Chrome Through Window Geometry Physics
For years, bot detection relied on basic checks to catch headless browsers. Scrapers hid behind Puppeteer or Playwright, patched navigator.webdriver to undefined, deleted "HeadlessChrome" from their User-Agent string, and called it a day.
Those days are long gone.
Modern anti-bot engines (including Verisoul, DataDome, and Cloudflare) don't care what your User-Agent claims. Instead, they examine the fundamental physical constraints of the operating system window manager: window chrome, frame insets, and display coordinates.
In this article, we will break down how Chromium's new headless architecture (--headless=new) works under the hood, and how its synthetic window frame implementation leaks headless execution every single time.
# The Geometry of a Real Browser Window
When you launch Google Chrome on a desktop operating system (Windows, macOS, or Linux), the operating system window manager decorates the application window.
A browser window consists of two areas:
- The Outer Window (
window.outerWidth,window.outerHeight): The total physical dimensions of the window on your screen, including the title bar, address bar (omnibox), bookmark bar, tab strip, and OS resizing borders. - The Inner Viewport (
window.innerWidth,window.innerHeight): The actual pixel area where web page HTML and CSS render.
Because of this physical window chrome:
- ◆On Windows: The top navigation bar, tabs, and window borders mean
outerHeightis substantially larger thaninnerHeight(typically by 70 to 120 pixels depending on bookmarks and UI scaling). - ◆On macOS: The rounded title bar and tab interface mean
outerHeightexceedsinnerHeightby 80 to 110 pixels. - ◆Horizontal borders: Window resizing drop shadows and borders mean
outerWidthis 16 to 30 pixels wider thaninnerWidth.
In a headed browser with a human sitting at the screen, outerWidth === innerWidth and outerHeight === innerHeight is physically impossible, unless the window is in true fullscreen mode.
# What Happens in --headless=new?
In older versions of Chromium (--headless=old or headless-shell), Chrome did not render any window chrome at all. Both outerWidth and innerWidth were identical, making headless instances trivial to catch.
To fix this, Google introduced --headless=new. Instead of rendering without window chrome, Chromium simulates a desktop window using a C++ class called:
HeadlessModeBrowserWindow
Because there is no real OS window manager (like Windows DWM, macOS Quartz Compositor, or Linux X11/Wayland) attached to a physical display, Chromium's C++ code synthesizes mock non-client frame insets mathematically.
And that is where the leak occurs.
# The Synthetic Frame Inset Signatures
Chromium's HeadlessModeBrowserWindow uses hardcoded geometry formulas for each platform:
1. Windows Synthetic Insets
On Windows headless instances, Chromium synthesizes frame insets using:
Insets(top: 87, left: 9, right: 9, bottom: 9)
This generates very specific dimensional math:
- ◆Width difference (
outerWidth - innerWidth): Left border (9px) + Right border (9px) = exactly 18px - ◆Height difference (
outerHeight - innerHeight): Top title bar (87px) + Bottom border (9px) = exactly 96px (or 105px on standard high-DPI scaling)
If you inspect a browser that claims to be a normal headed desktop browser on Windows, and outerWidth - innerWidth === 18 while outerHeight - innerHeight === 96, it is running Chromium's synthetic headless frame code.
2. macOS Synthetic Insets
On macOS headless instances, Chromium synthesizes frame insets using:
Insets(top: 87, left: 13, right: 13, bottom: 13)
This generates:
- ◆Width difference (
outerWidth - innerWidth): Left border (13px) + Right border (13px) = exactly 26px - ◆Height difference (
outerHeight - innerHeight): Top bar (87px) + Bottom border (13px) = exactly 100px (or 109px with DPI scaling)
Real macOS window chrome never creates a 26px horizontal inset on a standard Chrome window.
3. Linux Ozone Offscreen Frame
On Linux headless runners, Chromium's Ozone abstraction layer renders offscreen without side borders:
- ◆Width difference:
outerWidth === innerWidth(0px delta) - ◆Height difference:
outerHeight - innerHeight === 87px
Furthermore, in headless Linux environments, virtual displays lack system taskbars (screen.availHeight === screen.height), and the window coordinates are pinned strictly to the top-left corner (screenX === 0, screenY === 0).
# The 800x600 Default Dimension Trap
When an automated script launches Chromium in headless mode without explicitly setting --window-size, upstream Chromium defaults to virtual bounds of 800x600.
If you combine the 800x600 default bounds with the 18px horizontal inset formula, you get:
- ◆
outerWidth === 800 - ◆
outerHeight === 600 - ◆
outerWidth - innerWidth === 18
No human user in 2026 runs a desktop browser window at exactly 800x600 with an 18px synthetic non-client inset.
# Writing a Deterministic Headless Detector
To build a reliable headless detector, we first verify that the browser is not in legitimate fullscreen (either via F11 display fullscreen or DOM element fullscreen). Then we test for Chromium's synthetic frame insets:
window.detect_headless = async function () {
try {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return false;
}
const { outerWidth: ow, outerHeight: oh, innerWidth: iw, innerHeight: ih } = window;
if ((ow === 0 && oh === 0) || (iw === 0 || ih === 0)) return true;
// Guard: Check for genuine fullscreen
const isDocumentFullscreen = Boolean(
document.fullscreenElement ||
document.webkitFullscreenElement ||
window.matchMedia?.('(display-mode: fullscreen)')?.matches
);
const isDisplayFullscreen = typeof screen !== 'undefined' && Boolean(
screen.width > 800 && screen.height > 600 &&
iw === screen.width && ih === screen.height &&
(screen.availWidth < screen.width || screen.availHeight < screen.height)
);
const isGenuineFullscreen = isDocumentFullscreen || isDisplayFullscreen;
if (!isGenuineFullscreen && window.self === window.top) {
const wDiff = ow - iw;
const hDiff = oh - ih;
const dpr = window.devicePixelRatio || 1;
const rawWDiff = Math.round(wDiff * dpr);
const rawHDiff = Math.round(hDiff * dpr);
// Invariant 1: Upstream Chromium default virtual window bounds (800x600)
if (ow === 800 && oh === 600 && (wDiff === 18 || rawWDiff === 18 || ow === iw || hDiff >= 87)) {
return true;
}
// Invariant 2: Windows synthetic insets Insets(87, 9, 9, 9)
if ((wDiff === 18 && (hDiff === 96 || hDiff === 105)) ||
(rawWDiff === 18 && (rawHDiff === 96 || rawHDiff === 105))) {
return true;
}
// Invariant 3: macOS synthetic insets Insets(87, 13, 13, 13)
if ((wDiff === 26 && (hDiff === 100 || hDiff === 109)) ||
(rawWDiff === 26 && (rawHDiff === 100 || rawHDiff === 109))) {
return true;
}
// Invariant 4: Linux Ozone synthetic top bar with zero-origin coordinates
const isSyntheticLinuxTop = (hDiff === 87 || rawHDiff === 87) && ow === iw;
const isZeroOrigin = window.screenX === 0 && window.screenY === 0;
const hasNoTaskbar = typeof screen !== 'undefined' &&
screen.availWidth === screen.width &&
screen.availHeight === screen.height;
if (isSyntheticLinuxTop && isZeroOrigin && hasNoTaskbar) {
return true;
}
// Invariant 5: Zero frame insets outside fullscreen (headless-shell)
if (ow === iw && oh === ih) {
return true;
}
}
// Guard: Minimal headless shell missing window.chrome
const isBlink = Boolean(window.chrome || 'webkitRequestFileSystem' in window);
if (isBlink && (!window.chrome || (navigator.plugins?.length === 0 && navigator.pdfViewerEnabled === false && ow === iw))) {
return true;
}
return false;
} catch (_) {
return false;
}
};# Conclusion
Bot protection has evolved far beyond string matching. Platforms like Verisoul measure how window physics interact with layout engines.
If you want to run high-throughput automation without getting flagged as headless, you cannot simply pass --headless=new to Chromium and hope for the best. You need to either control genuine headed window instances via virtual framebuffers (like Xvfb on Linux) or modify Chromium's HeadlessModeBrowserWindow C++ implementation to mimic native OS window metrics dynamically.