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.

CodeWeightWhat it meansWhy the weight is what it is
headless_renderer90WebGL renderer is a software rasteriserA consumer desktop browser with no GPU is a container
headless_notif_contradiction45Notification permission says denied while the permissions API says promptNo real profile is in both states at once
headless_no_window_chrome40No browser-specific window object under a Chrome User-AgentStrong, but trivially faked
headless_zero_outer40The outer window width or height is zeroAlso legitimately zero in some cross-origin frames
headless_no_languages25The languages list is missing or emptyReal browsers always populate it
headless_no_plugins20Zero plugins under a desktop Chrome User-AgentModern Chrome exposes five PDF entries
headless_no_concurrency15Hardware concurrency is zero or absent under a Chrome User-AgentSome privacy-focused browsers clamp this value
headless_screen_eq_avail10Available screen size exactly equals screen size on a desktop User-AgentAlso true of Linux kiosks and full-screen presentations
headless_no_chrome_runtime10No runtime object under a Chrome User-AgentIts 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_mismatchThe 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
CodeWeightWhat it means
human_mouse_natural−25Sampled pointer positions vary in a way a straight line cannot explain
mouse_linear40Nine in ten sampled triples are exactly collinear — what an interpolating driver produces and a hand never does
mouse_static25The pointer fired move events but never changed pixel
no_interaction20Zero interactions across the whole session, added server-side because only the server knows the session ended
no_scroll_tall_page10A page half again as tall as the viewport that was never scrolled
beacon_forged90The payload claimed time that provably did not exist

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