The signal codes

Automation markers, headless tells, UA-claim verification, consistency checks and human evidence.

These are the codes the beacon reports about the browser it is running in. A code is an observation, not a verdict: the scorer correlates them with the transport and behaviour planes and owns the final number.

The weight column here is only this plane’s contribution to the client sub-score. The rules that turn them into a verdict are on the scoring page, and the identifiers that land on a session are on identifiers and labels.

01 · Definitive automation markers
CodeWeightWhat it means
automation_webdriver100The navigator webdriver flag is true
automation_cdc100chromedriver’s marker property on window or document
automation_playwright100A Playwright binding
automation_puppeteer100A Puppeteer binding
automation_selenium100A Selenium, webdriver, driver or fxdriver binding
automation_nightmare100The Nightmare binding
automation_phantom100A PhantomJS binding
automation_domauto100A DOM automation controller
Presence is certainty. Absence means nothing at all.

Hiding these is a one-line patch that every serious scraper applies. They are cheap, they catch the lazy majority, and they are the reason the other planes exist.

02 · Headless-browser tells
automationmarkersheadlesstellsengine claimprobesconsistencycross-checkshumanpresenceTHE SCORER OWNS THE VERDICTa code is an observation, never a verdict on its own

Figure 1 — five families of observation, one scorer. The beacon reports raw measurements and the server does the comparing, because the server holds the authoritative User-Agent and a client cannot suppress a comparison it never performs.

headless_renderer

WebGL renderer is a software rasteriser

Weight
90
Why the weight is what it is
A consumer desktop browser with no GPU is a container
headless_notif_contradiction

Notification permission says denied while the permissions API says prompt

Weight
45
Why the weight is what it is
No real profile is in both states at once
headless_no_window_chrome

No browser-specific window object under a Chrome User-Agent

Weight
40
Why the weight is what it is
Strong, but trivially faked
headless_zero_outer

The outer window width or height is zero

Weight
40
Why the weight is what it is
Also legitimately zero in some cross-origin frames
headless_no_languages

The languages list is missing or empty

Weight
25
Why the weight is what it is
Real browsers always populate it
headless_no_plugins

Zero plugins under a desktop Chrome User-Agent

Weight
20
Why the weight is what it is
Modern Chrome exposes five PDF entries
headless_no_concurrency

Hardware concurrency is zero or absent under a Chrome User-Agent

Weight
15
Why the weight is what it is
Some privacy-focused browsers clamp this value
headless_screen_eq_avail

Available screen size exactly equals screen size on a desktop User-Agent

Weight
10
Why the weight is what it is
Also true of Linux kiosks and full-screen presentations
headless_no_chrome_runtime

No runtime object under a Chrome User-Agent

Weight
10
Why the weight is what it is
Its presence on ordinary pages has changed across releases

Only the strong four — the renderer, the missing window object, the notification contradiction and the zero outer size — plus any automation marker set the headless boolean. The weak ones each have a real population of genuine humans behind them, and a boolean that is wrong for real visitors is worse than no boolean.

03 · User-Agent claim verification
CodeWeightWhat it means
ua_older_engine85The User-Agent claims a version whose features the engine does not have
ua_newer_engine85The engine has features that shipped after the claimed version
ua_probe_<major>0Which probe caught it — diagnostic only

A scraper can set any User-Agent string it likes, but it cannot retrofit a JavaScript engine. The probe table pairs a browser major version with a function that first shipped natively in it:

var UA_PROBES = [
    63,  'Promise.prototype.finally',
    69,  'Array.prototype.flat',
    73,  'Object.fromEntries',
    85,  'String.prototype.replaceAll',
    93,  'Object.hasOwn',
    98,  'structuredClone',
    110, 'Array.prototype.toSorted',
    122, 'Set.prototype.union'
];

Given a User-Agent claiming version N: every entry at or below N minus two must be present, or the engine is older than claimed; every entry at or above N plus two must be absent, or the engine is newer than claimed. The grace of two majors means a browser mid-upgrade, an enterprise pin, or one of the browser’s own User-Agent reduction quirks is never flagged.

Adding a row as the browser advances

Roughly every ten releases, pick a feature that is shipped in a known version — look it up rather than guessing, because a wrong version number here manufactures false accusations against real people — that is reachable as a function by a dotted path, and that is not commonly polyfilled. Never remove old rows; they are what catches ancient engines.

Two guards you must not remove either

The polyfill guard: the probe requires the function to report as native code, or a site loading a compatibility library would make an old engine look new and get its own visitors flagged. The iOS exclusion: every browser on iOS is one engine wearing another’s User-Agent, so their feature set has nothing to do with the version in the string and they are skipped outright. Flagging them would be a pure false positive on millions of real phones.

04 · Consistency cross-checks, evaluated on the server
CodeWeightWhat it means
platform_mismatch35The reported platform contradicts the operating system in the User-Agent
touch_missing_mobile30A phone or tablet User-Agent on a device with no touch support
screen_outer_impossible25The window is larger than the screen it sits on
dpr_odd10The device pixel ratio is absent, zero or not a finite number
tz_mismatch—The browser timezone disagrees with the one derived from the address
tz_unknown5The browser gave no timezone at all

The beacon reports the raw measurements and the server does the comparing. Three reasons, and byte count is the least of them: the server holds the authoritative User-Agent read off the connection, and half these checks compare something against the User-Agent, which is precisely the thing we do not trust; a client cannot suppress a comparison it never performs; and thresholds can be tuned server-side without asking every site to redeploy a script tag.

Two conservatism rules apply here. The reverse of the touch check is deliberately not performed, because a desktop User-Agent with touch would flag every touchscreen laptop. And the display checks only run when the payload actually reported a screen size — a truncated write or an older client sends zeroes, and “no data” must never be read as “a window with no size”.

05 · Human-presence evidence
human_mouse_natural

Sampled pointer positions vary in a way a straight line cannot explain

Weight
−25
mouse_linear

Nine in ten sampled triples are exactly collinear — what an interpolating driver produces and a hand never does

Weight
40
mouse_static

The pointer fired move events but never changed pixel

Weight
25
no_interaction

Zero interactions across the whole session, added server-side because only the server knows the session ended

Weight
20
no_scroll_tall_page

A page half again as tall as the viewport that was never scrolled

Weight
10
beacon_forged

The payload claimed time that provably did not exist

Weight
90

Mouse positions are sampled at most once per second, up to sixteen points, and the classifier stays silent below six samples: somebody who nudged the mouse twice is not evidence of anything. The coordinates never leave the browser — only the verdict does.

Every “unknown” is recorded as unknown

Throughout the beacon, a probe whose interface is missing, blocked or throwing records nothing at all. A false “this human is a bot” is far worse than a missed bot, and every ambiguous case in this codebase is resolved in that direction.

Loghound is open source and MIT licensed. Questions about the Opensolr half — the account, the indexes, the plan — go to opensolr.com/contact; questions about the software itself belong on GitHub.

Loghound Documentation