The scoring rules

Weighted additive, nought to a hundred, and eighty or more means the signal alone is a verdict.

Weighted, additive, nought to a hundred, capped at a hundred. Every point added carries a reason code, and a verdict with no reasons is a bug rather than a terse answer.

Why the numbers are what they are

The weights follow one principle: eighty or more is a claim that this signal alone is enough to call something a bot, because eighty is the bot threshold. So the question for every rule is not “how suspicious is this” but “am I willing to publish a verdict on this evidence and nothing else”. Eleven of the twenty-five clear that bar. Everything below eighty is designed to stack — three weak signals reaching ninety is the intended path for a scraper that leaves no single decisive mark.

The panel shows human labels rather than these identifiers. Identifiers and labels is the mapping, and it is the page to read if you are writing your own query or reading a filter URL.

01 · Decisive alone — eighty or more
100

A definitive driver artefact was exposed to the page: the webdriver flag, a chromedriver global, or a Puppeteer, Playwright or Selenium hook. Browsers do not have these; drivers do. There is no false positive here — a page cannot acquire them by accident.

Label
Automation marker
100

It said it was a bot and it was telling the truth. Verdict bot, threat none. See section 04.

Label
Declared crawler
100

The log records a User-Agent, and it is neither a browser a person could be using nor a declared crawler — an empty header, a bare Mozilla/5.0, WordPress/6.4.3, a home-made client. The browser recognition behind it covers every rendering engine and every desktop, mobile, regional, in-app, privacy and text-mode browser, TVs, consoles and feature phones, and it errs towards “browser”. A log format that does not record the User-Agent is never judged by it. Class undeclared client.

Label
Not a browser
95

It declared itself a major search crawler and forward-confirmed reverse DNS did not back that up. This is impersonation, not automation, and forward-confirmed reverse DNS is the mechanism the search engines themselves document for verifying their crawlers.

Label
rDNS claim failed
90

WebGL reported a software rasteriser — SwiftShader, llvmpipe, Mesa OffScreen or Microsoft Basic Render — which is what you get when there is no screen. Not 100, because a virtual machine, a remote desktop session or broken graphics drivers can legitimately land here.

Label
Headless renderer
90

The claimed dwell time is impossible against the issue time of its own token, by more than a five-second tolerance. Within that tolerance the number is clamped silently and nothing fires, because one-second token resolution plus a heartbeat queued behind a busy main thread produces honest overshoot. The lie is recorded rather than discarded, because the lie is the evidence.

Label
Forged beacon timing
85

The engine lacks a feature that shipped in the browser version the User-Agent claims, or has one that shipped after it — with a grace of two major versions in each direction, and iOS browsers skipped outright because they are all one engine wearing another’s User-Agent. A spoofed User-Agent cannot retrofit a JavaScript engine.

Label
UA claim failed
85

The session requested something no browser asks for by accident: a JNDI lookup, a credentials file, a published CVE path, shell syntax with a shell verb, a server-side-fetch address, a User-Agent that names a fuzzer, or a pattern from your own attack patterns. The noisier patterns are listed on the Attacks view and are not worth a verdict on their own.

Label
Exploit probe
85

The site answered a request that matched an attack pattern with a 403 or a 406. The site’s own defences refused it and the detector names it a probe — two independent judgements agreeing about one request. Sign-in requests do not count, because a person can be refused at a login form.

Label
Refused attack
80

Five or more distinct addresses shared this exact header fingerprint within twenty-four hours on non-mobile networks, and it is not a crawler whose reverse DNS check passed. One client, many exits.

Label
Proxy fleet fingerprint
80

The site refused every single request this client made and served it nothing — no page, no redirect, not one byte of body. Somebody browsing a site gets something back. Authentication challenges and rate-limit responses do not count towards it on their own.

Label
Never served anything
02 · Designed to stack — below eighty
75

A Chrome User-Agent arrived without the matching client hint, or with one that contradicts it. Chrome always sends its own client hints on a secure origin. Requires the header to be logged.

Label
Sec-CH-UA mismatch
70

The platform client hint disagrees with the operating system the User-Agent claims. Windows in the User-Agent and Linux in the hint is a headless container in a costume.

Label
Platform mismatch
70

An HTML page was served with a 200, the User-Agent claimed a real browser, and no beacon ever arrived — and the beacon is known to be deployed on this site. That gate is not optional: on a site that never installed the snippet, every session looks like this and the rule would condemn the whole audience. Weighted 70 for the same reason in miniature, so it cannot condemn anybody on its own.

Label
No JS on HTML
50

Every request in the session matched a named attack pattern or a well-known admin path, the server answered none of them with a body, and the client fetched no stylesheet, script, font, image or favicon. A person whose browser lands on a 404 still renders the error page.

Label
Refused probe sweep
45

Five or more page and endpoint requests, at least half of them refused, with only occasional success — something trying paths until one works. Assets are not counted, so a reader who meets a few broken images stays out of it, and neither are sign-in or rate-limit refusals.

Label
Mostly refused
45

A consumer browser User-Agent arriving from hosting address space. People do browse from servers, so this is not decisive — but it is unusual enough to be worth stacking.

Label
Datacentre + browser UA
45

Four or more gaps between requests, a median gap of at least a second, and a spread below ten per cent of that median. Relative rather than absolute, because a 200 ms rhythm and a 30 s rhythm are equally mechanical; the median floor keeps a burst of asset fetches from looking like a metronome. People are not metronomes.

Label
Periodic timing
40

The site answered this client with 403 Forbidden, and no attack pattern explains it. Visitors almost never meet one on a public site, but a captcha wall or an address rule can refuse a person too, so it adds points and never convicts on its own.

Label
Got a 403
40

The beacon ran, the session ended, and not one scroll, click, key press or touch ever happened. Possible for a human reading a page on a static screen, which is why it is 40 and not 80.

Label
No interaction
40

A User-Agent claiming a desktop operating system arriving from cellular address space. Weak alone — tethering a laptop to a phone looks exactly like this — and meaningful when it stacks, because a mobile network is also where an address rotates fastest.

Label
Desktop on a mobile carrier
30

At least two assets were re-requested with no conditional header ever sent, and this site is known to answer with a not-modified status at all. Both extra conditions exist to avoid blaming the client for the server’s behaviour.

Label
No conditional requests
25

HTML was fetched and not a single stylesheet, script, font or image followed it. The classic scripted-client signature — only 25, because a warm cache, a text-only browser or a prefetch can all look like this.

Label
No sub-resources
15

One page, gone in ten seconds or less. Weak alone and it is supposed to be weak — plenty of humans bounce. It exists to push a session that has already accumulated other signals over a threshold.

Label
Single page, under 10s
10

The browser timezone disagrees with the timezone derived from the address. The weakest signal in the table and priced accordingly: a VPN, a traveller, a clock set by hand or an IP database that is simply wrong all look exactly like this. It corroborates other evidence and can no longer hold a visit below human on its own.

Label
Timezone mismatch
03 · Thresholds, and the ladder
BOT SCOREHUMANLIKELY HUMANUNKNOWNLIKELY BOTBOT020406080100Eleven rules land at or above 80 on their own. Everything below it has to stack:no sub-resources 25 + no conditional requests 30 + got a 403 40 = 95, a bot on three weak signals.

Figure 1 — the verdict bands. Unknown is a real answer and it is used. A session with 45 points is not “probably a bot”; it is a session Loghound does not have enough evidence about, and saying so is more useful than guessing.

A session also carries a class saying what kind of automation it was — headless browser, scripted client, declared crawler, AI crawler, monitor, spoofed User-Agent, proxy fleet, undeclared client, or not automation at all. The vocabulary page lists them with what each one means.

04 · Honest crawlers are not the enemy

Major search crawlers and AI crawlers identify themselves. They get the verdict bot — they are bots, and counting them as human traffic is how analytics dashboards end up lying to you — with a class that says which kind.

The panel presents them separately from evasive traffic, always

Conflating “a search engine indexed four hundred pages” with “someone is scraping you from two hundred residential addresses” is precisely what makes existing tools useless for this. They are opposite facts: one is a service you want, the other is a cost you are paying. The separation is enforced in the ruleset rather than by a checkbox in the interface, because every consumer of a session document must see the same split.

An AI-crawler flag is set for around thirty named agents, so “how much of my content is being taken for model training” is one facet away. Note that several of those are user-triggered fetchers rather than training crawlers — they fetch a page because a person asked an assistant about it. They are grouped together because operators consistently want them in one bucket, but the two are not the same event.

Software that acts for a person without being one — a password or passkey agent, a search prefetch proxy, a load balancer health check, an Android app’s HTTP stack — also names itself, and is filed with the declared crawlers rather than as an undeclared client.

05 · Scoring a session that has not ended yet

A session document is written for an open session too, on every scorer run, so the panel shows traffic within a minute of ingestion starting rather than after the thirty-minute idle timeout. Such a document is marked provisional, and it is scored differently.

Eight of the twenty-five rules read something a session may still go on to change, and every one of them would accuse a live human visitor:

Deferred ruleWhat its absence means while the session is open
No JS on HTMLThe beacon reports on page hide — a visitor still reading has not sent one
No sub-resourcesThe sub-resource log lines have not been written yet
No conditional requestsNothing has been re-fetched yet, and no conditional response has arrived yet
No interactionThe visitor has not scrolled or clicked yet
Single page, under 10sEvery session is one page and under ten seconds at second one
Refused probe sweepA stylesheet or a page with a body may still follow the refused request
Never served anything“Every request so far was refused” says nothing about the 200 that may come next
Mostly refusedThe ratio is still being counted over a visit that is still growing

So a provisional verdict rests only on evidence already present, and it is floored at unknown: it may reach likely bot or bot on positive evidence — an automation marker, a non-browser User-Agent or a refused attack is still called immediately — but it may never claim human or likely human, because “nothing incriminating yet” on one request is not an acquittal. When the floor applies, the reasons carry a code saying so, exactly as a clean session carries one saying nothing fired.

The same discipline applies to a beacon-only session, where the eleven rules that read the request log are silenced instead.

Next: how to change a weight, and who this ruleset gets wrong.

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