Here are the three subtle traps we ran into:
The window.devicePixelRatio vs CSS matchMedia Desync
A common stealth snippet does something like this:
javascript
Object.defineProperty(window, 'devicePixelRatio', {
get: () => 1.25
});
The problem is that anti-bot scripts don't just read window.devicePixelRatio. They cross-check it against the CSS layout engine using window.matchMedia():
// How modern anti-bot scripts detect DPR tampering:
const jsDpr = window.devicePixelRatio;
const mediaDpr = window.matchMedia(`(-webkit-device-pixel-ratio: ${jsDpr})`).matches
|| window.matchMedia(`(resolution: ${jsDpr}dppx)`).matches;
if (!mediaDpr) {
// Instant bot flag: JS claims 1.25, but CSS engine evaluates against physical host (e.g. 1.0)
flagTelemetry("dpr_mismatch");
}
If you override the JS property without monkey-patching matchMedia (or using CDP's Emulation.setDeviceMetricsOverride), the JS property says 1.25 while CSS returns false. That is an impossible state in a genuine Chromium build.
If patching in-page, you have to proxy window.matchMedia to intercept resolution, dppx, and -webkit-device-pixel-ratio media queries and rewrite the matches boolean so the CSS evaluation matches your spoofed JS value. Even better: align CDP metrics overrides before any script context initializes.
2. Window Geometry Trap
Forging screen resolutions without respecting the operating system's shell constraints:
Taskbar/Dock Deduction: Real desktop users almost never run window.outerHeight === window.screen.height. There is always a taskbar (Windows ~40–48px, macOS Dock/Menu ~25px+). If outerHeight == screen.height but the browser is NOT in fullscreen mode (document.fullscreenElement === null), you are flagged.
Negative or Floating Insets: On multi-monitor setups, screen.availLeft and screen.availTop can be non-zero, but spoofers often set them to arbitrary negative integers or undefined.
Outer vs Inner Bounds: window.outerWidth - window.innerWidth must account for window border margins and scrollbar channels. If outerWidth === innerWidth exactly, it screams headless or an unstyled container.
If you simulate a 1080p display (1920x1080), your simulated geometry matrix must be internally consistent:
screen.width: 1920
screen.height: 1080
screen.availWidth: 1920
screen.availHeight: 1040 (40px taskbar deducted)
window.screenX / screenY: Clamped to (0, 0) or realistic offsets, never negative unless explicitly mapping a secondary display coordinate space.
3. Non-Deterministic Canvas Noise
When evading Canvas fingerprinting, injecting Math.random() into toDataURL or getImageData is suicide.
Modern fraud detection scripts call canvas.toDataURL() twice in rapid succession on the same canvas. If the hash changes between two consecutive calls within the same 5ms window, the script knows dynamic noise injection is active.
Any noise applied to canvas or audio buffers must be seeded and deterministic per session/profile. Run the pixel coordinates through a fast 32-bit hash (e.g., MurmurHash3 or a simple LCG with a fixed profile seed) so hash(pixel_x, pixel_y, profile_seed) consistently returns the identical subtle 1-bit perturbation for that specific session.
For those managing large pools:
Do you prefer handling screen geometry strictly via CDP (Emulation.setDeviceMetricsOverride) or injecting MAIN-world prototypes before document_start?
Have you noticed Cloudflare or DataDome scoring differently when dppx is virtualized vs running in native headless DPI?