liarjs.dev

Field notes

Deep-dives on how browser fingerprinting and bot detection actually work, one dimension at a time, grounded in reverse-engineering real detectors and testing real anti-detect browsers. New posts land about once a week.

Fonts

Beyond width: the other eleven numbers TextMetrics gives you

Metric-compatible substitutes match advance widths by design. They do not match the font bounding box, the ink extents or the baselines, and measureText returns all of those from the same call.

October 14, 2026 · 6 min read  → read
Network layer

The layer JavaScript never sees: TLS, JA3/JA4 and HTTP/2 fingerprints

Before a line of your JS runs, the TLS handshake and HTTP/2 frames have already fingerprinted the client. It's the wall a JS-layer spoof can't touch and the place where a real Chrome build pays off.

October 10, 2026 · 10 min read  → read
Engineering

Three majors behind: version lag is a fingerprint of its own

Chrome ships a major every four weeks and updates itself without asking. A browser several versions back is not a user who forgot, it is a pinned build, and that narrows the anonymity set before anything else is read.

October 7, 2026 · 6 min read  → read
Geometry

DOMRect and subpixel geometry: the fingerprint DataDome loves and the dpr trap that breaks spoofers

getClientRects returns floating-point layout geometry that varies by device and CreepJS hides a hardcoded hash that only fires when devicePixelRatio equals 1.

October 3, 2026 · 8 min read  → read
Method

Which signal actually blocked you: running a controlled bisect

A rejection tells you nothing about which of forty signals caused it. One variable at a time, a control arm on both ends, and the discipline to accept that four fixes in a row changed nothing.

September 30, 2026 · 6 min read  → read
Workers

Workers and OffscreenCanvas: fingerprinting in the thread you forgot to patch

Detectors re-read identity inside a Web Worker and re-run canvas in OffscreenCanvas, a favorite way to catch spoofs that only hooked the main thread.

September 26, 2026 · 9 min read  → read
Navigator

The plugin face Android never had: five entries that kill a mobile persona

Chrome on Android is built without the PDF viewer, so navigator.plugins is empty, mimeTypes is empty and pdfViewerEnabled is false. A desktop build wearing a phone persona reports 5, 2 and true.

September 23, 2026 · 6 min read  → read
Navigator

The plugin face: modern Chrome's five PDF entries and the version it betrays

navigator.plugins looks vestigial, but its exact shape is pinned to the Chrome version, and a stale plugin face is a clean contradiction detectors love.

September 19, 2026 · 8 min read  → read
Behavior

Static says phone, events say mouse: the device-class contradiction

A mobile persona sets maxTouchPoints and pointer:coarse and passes every static check. Then the page listens, and every event that arrives is a mouse event from a machine with a mouse.

September 16, 2026 · 6 min read  → read
Architecture

[native code]: why JS-override stealth loses to engine-level spoofing

Every getter you redefine in JavaScript leaves a residue a lie-detector can read. The split between JS-patched and engine-level browsers shows up in one toString() call.

September 12, 2026 · 9 min read  → read
WebGL

Mobile texture formats on a desktop GPU: the capability table under the string

Rewriting UNMASKED_RENDERER takes one line. The extension list, the WebGPU feature set and the shader precision table underneath it are produced by the driver, and they name the chip that is really there.

September 9, 2026 · 6 min read  → read
Geolocation

Timezone vs IP: the cross-layer check that catches proxies

A US exit IP paired with an Asia/Singapore browser clock is one of the cheapest, most reliable bot tells there is, and two well-funded anti-detect browsers shipped exactly that.

September 5, 2026 · 9 min read  → read
Screen

A window cannot be bigger than its screen: the geometry invariants

Screen spoofing usually rewrites screen.width and stops there. The window, its position, the menu bar inset and the multi-monitor flag all keep describing the real display, and they have to agree with each other.

September 2, 2026 · 6 min read  → read
Audio

Audio fingerprinting: the oscillator, the compressor and why silence must stay silent

The Web Audio fingerprint is stable to many decimal places and the fastest way to fail it is to add noise where a real browser outputs pure zeros.

August 29, 2026 · 9 min read  → read
Mobile

The interface census: an Android persona is a different build, not a different User-Agent

Chromium decides which Web APIs exist per platform, at build and configuration time. A desktop binary running a phone persona still answers typeof navigator.hid with 'object', and no identity layer moves that answer.

August 26, 2026 · 7 min read  → read
Fonts

Named but not measurable: the font check every cross-OS persona fails

An allow-list can put Segoe UI in the enumerable font set on a Mac. It cannot give it metrics. Every family the host has no file for measures the same width, and that one number is enough to call the persona a lie.

August 19, 2026 · 9 min read  → read
WebGPU

WebGPU, the new GPU oracle: adapter.info and the cross-interface triad

WebGPU exposes a second, independent view of your GPU. If it disagrees with WebGL and the UA platform, the spoof falls apart. Most anti-detect browsers shipped that mismatch for years.

August 19, 2026 · 9 min read  → read
WebGL

WebGL fingerprinting: it's the whole parameter set, not just the renderer string

Spoofing UNMASKED_RENDERER is the easy 5%. Detectors read dozens of getParameter values, shader precision and the actual rendered pixels and 'self-consistent' is not the same as 'believable'.

August 12, 2026 · 9 min read  → read
Automation

Automation tells are orthogonal: CDP, Runtime.enable and the isolated world

A flawless fingerprint won't hide the driver. navigator.webdriver, CDP execution-context leaks and headless tokens are a separate axis every serious detector checks.

August 7, 2026 · 12 min read  → read
Fonts

Font fingerprinting without a font API: the offsetWidth measurement method

The dominant font-enumeration technique never calls a font API at all. It measures span widths. That is why patching document.fonts alone gets you caught, and why CJK fonts leak your real OS.

August 5, 2026 · 8 min read  → read
Lie detection

Inside the CreepJS lie detector: four checks deterministic noise can't beat

CreepJS doesn't only collect signals. It proves them self-consistent. Here are the four invariant classes that catch spoofers even when their noise is perfectly stable.

July 29, 2026 · 10 min read  → read
Canvas

Canvas fingerprinting and the deterministic-noise rule

Anti-detect browsers get caught on canvas because their noise changes between two identical reads. The size of the noise is almost beside the point. Determinism is the whole game.

July 22, 2026 · 9 min read  → read
Foundations

Two layers, one browser: why consistency beats any single fingerprint

A perfect JavaScript fingerprint still fails if it disagrees with the network layer. Modern detection is a cross-layer consistency problem, not a collection problem.

July 15, 2026 · 8 min read  → read