Back to Knowledge Hub
Fingerprint

Why One Odd Signal Never Convicts a Browser

One probe on a public detection ruleset: a heavy environment tell scores 0.7 and still cannot convict, while one cross-layer contradiction goes straight to a verdict.

BotBrowser Team

Documentation

Want the structured docs for Fingerprint?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

BotBrowser supports replaying an authorized same-origin fixture under a declared profile and keeping a bounded record of which signals a page exposed, but it cannot make an environment signal weigh more than the ruleset allows, cannot turn a corroborating tell into a convicting one, and cannot stop a network-side contradiction from deciding the verdict before the page finishes loading. This review reads one public detection ruleset as data, reimplements its scoring arithmetic, and records which categories are allowed to convict, why a tell weighted 0.7 stops short of a verdict on its own, what one extra artifact does to the same signals, and how a single cross-layer contradiction clears the conviction line by itself.

TL;DR

  • The ruleset carries 126 rules across seven categories, and only three of them may convict: coherence, automation and artifact. The other four can only corroborate.
  • A page can be charged with 95 of the 126 rules, which means the 31 network and reputation rules never run in a browser-side check.
  • One environment tell at weight 0.7 scores 0.7 and lands on suspicious, because an environment rule cannot convict no matter how heavy it is.
  • Two environment tells together score 0.865 and still stay suspicious, which shows the gate rather than the arithmetic is what holds the line.
  • Adding one artifact rule to those two environment tells produces 0.96 and bot, with the environment weights unchanged.
  • One automation tell at weight 0.9 convicts on its own, so a page that leaks navigator.webdriver has no headroom at all.
  • Only 14 of the 126 rules span more than one layer, and those are the ones whose weight is amplified: 0.7 becomes 1.0.
  • The score does not depend on the order the rules are listed in, so a record has to name which rules fired rather than how many.

Contents

A first panel shows 126 rules in seven categories with only coherence, automation and artifact marked as able to convict. A second shows one environment tell at 0.7 staying suspicious and two at 0.865 still suspicious. A third shows an artifact added to the same pair reaching 0.96 and a bot label. A fourth shows a cross-layer rule whose 0.7 weight becomes 1.0 when two layers disagree.

Which categories are allowed to convict

A rule-based detector is a list of claims about a visitor, each carrying a weight and a category, and the category decides what a fired rule is allowed to do. The ruleset the probe reads has 126 rules in seven categories:

categories -> coherence=49 automation=24 artifact=15 behavioral=7 reputation=4 environment=26 prevalence=1
convicting -> coherence,automation,artifact (others corroborate only)

Three categories can convict, and the other four can only add to the score. That split is the whole design. An environment rule describes a surface that looks thin, such as a short plugin list, a missing voice list or a hardware concurrency value far below the machine's capability. Those surfaces really are thin on plenty of legitimate visitors: a stripped-down workstation image, a locked-down enterprise build, a browser with no audio device. Treating one as proof would mislabel those visitors, so the ruleset lets them raise the score and stops there. A detector library that names its own components makes the same distinction: BotD detector registry and component states records how a named detector reports what it observed rather than a single score.

The behavioral category works the same way. A still page that a reader is simply looking at produces little or no input, so a rule about keystroke entropy or pointer path cannot distinguish a person who is reading from a driver that injected nothing. The ruleset marks those tells as corroborating for the same reason.

An automation rule is different in kind. navigator.webdriver being true, or the CDP Runtime.enable state being visible, or a headless token sitting in the user agent, describes something that a normal browsing session does not do. Those are not thin surfaces that legitimate visitors sometimes have; they are states a browser only reaches when a driver holds it. The ruleset can therefore let them convict.

artifact covers the case a page actively contradicts itself: a native function whose source text is not the engine's, a worker that reports different values from the main page, a canvas that fails its own invariant. Each of those is an internal inconsistency rather than a thin surface, which is why it can convict.

coherence is the category for two layers disagreeing, and it is the largest at 49 rules. A TLS handshake that implies one operating system while the user agent claims another is a contradiction between what the connection says and what the page says. No legitimate visitor produces that, because the two layers are generated by the same client.

Why a heavy environment tell stops short

The interesting part is what the gate does to a heavy environment rule. The probe takes four environment tells and scores each one alone:

alone:br.webgl_not_angle -> weight=0.55 category=environment score=0.55 verdict=suspicious
alone:br.font_linux_leak -> weight=0.7 category=environment score=0.7 verdict=suspicious
alone:br.screen_zero -> weight=0.7 category=environment score=0.7 verdict=suspicious
alone:br.voices_empty -> weight=0.5 category=environment score=0.5 verdict=suspicious

The weight 0.7 rules clear the suspicious line at 0.35 and stop at 0.7, short of the 0.65 conviction line only because the gate refuses. Read the numbers alone and a 0.7 score looks like a strong finding; read the category and it is a surface that may simply be thin.

That distinction matters when a team reads a report that says "suspicious". A suspicious label from an environment tell is telling you the page looked unusual in a specific, named way. It is not telling you the page was automated, and a report that prints the score without the category invites exactly that misreading. A 0.7 is not 70% of the way to a conviction; it is a corroborating observation that has hit the ceiling its category allows.

The same probe shows that stacking environment tells does not fix this:

environment-pair -> weights=0.55+0.7 score=0.865 verdict=suspicious convicting=false

Two environment rules combine to 0.865 by the noisy-or formula, well past the 0.65 line, and the verdict is still suspicious. The printed convicting=false is the reason: no fired rule is in a convicting category, so the score is allowed to rise and the label is not. That is a deliberate design choice, and it is the one a team most often gets wrong when it assumes a higher number means a stronger conclusion.

The practical consequence is about where to spend effort. If a deployment is flagged only by environment tells, hardening the surfaces those rules describe is a losing game, because the rules are designed to accept thin environments as normal. The effort belongs on the convicting categories: keep navigator.webdriver and the CDP state clean, and keep every realm reporting the same values.

Where the conviction line actually sits

The probe adds one rule from a convicting category to the same two environment tells and the verdict flips:

plus-artifact -> score=0.96 verdict=bot convicting=true categories=environment,environment,artifact

Nothing about the environment tells changed. The two rules that scored 0.865 still score the same, and adding a third rule takes the total to 0.96 and the label to bot, because convicting is now true. The environment weights were never the deciding factor; the presence of one rule from a convicting category was.

The single-tell cases make the same point from the other direction:

alone:br.webdriver_present -> weight=0.9 category=automation score=0.9 verdict=bot
alone:br.cdp_runtime_enabled -> weight=0.85 category=automation score=0.85 verdict=bot
alone:br.ch_he_headless -> weight=0.85 category=automation score=0.85 verdict=bot

Each of those convicts alone, at a weight close to but below 1. There is no headroom: a page that leaks navigator.webdriver is convicted by one signal, and the only way back is to not leak it. That asymmetry is intentional, because the ruleset treats an automation state as a categorical fact rather than a probabilistic hint.

Stacking more corroborating tells on top raises the score without changing the label:

stack -> one=0.9 two=0.955 three=0.987

Three signals take the score to 0.987, and the verdict was already bot at one. A report that shows 0.99 for a page with three findings and 0.9 for a page with one is suggesting a difference in confidence that the ruleset does not make: both are convicted, and the extra tells add almost nothing to the decision. The score is a summary of how many rules fired, and the label is a separate gate.

Two of those environment tells deserve a concrete example, because "thin surface" is abstract until a team meets one in production. A locked-down enterprise image commonly ships without audio devices, so the voice list and the media device list come back empty; three environment rules fire on a machine that has never run a driver. A headless server build commonly reports a hardware concurrency value the operator pinned for capacity planning, and a container without GPU passthrough returns a software renderer whose name does not match the profile's device. Neither observation is wrong, and neither is evidence of automation, which is exactly why the ruleset places them in a category that cannot convict.

The asymmetry has a second consequence for a team choosing what to measure. Because an environment rule can only corroborate, testing a new deployment against environment rules alone is cheap and nearly useless: the signals are the ones that depend on hardware, container images and operator settings rather than on the browser. Testing against automation rules is more decisive but narrower, since those rules describe states a driver creates. The rules that actually distinguish one deployment from another are the coherence rules, and those are the ones a browser-side run cannot reach at all.

A third point is about what an operator can control. The environment tells are mostly outside the browser: they describe the image, the GPU, the audio stack and the host's capacity. An operator can document them and plan for them, but changing them means changing the deployment. The automation tells are inside the browser's reach, which is why they are worth hardening first. A profile cannot make a container report a nicer renderer name, but it can keep a driver from leaving its marks on the page.

What a cross-layer contradiction is worth

The last piece is the amplification applied to rules that span layers:

cross-layer rules -> 14 of 126
amplify:net.tls_vs_ua_browser -> layers=network+browser weight=0.7 effective=1
amplify:net.h2_vs_ua_browser -> layers=network+browser weight=0.6 effective=0.9
amplify:net.datacenter_origin_proxied -> layers=network+browser+reputation weight=0.8 effective=1

Fourteen of the 126 rules name more than one layer, and each of them has its weight multiplied by 1.5, capped at 1. A 0.7 rule becomes 1.0, and a 0.6 rule becomes 0.9. The amplification is the ruleset stating its thesis in arithmetic: a contradiction between two independently generated layers is worth more than a strong reading of one layer, because a single client generates both and cannot easily make them disagree on purpose.

A cross-layer rule convicts alone:

amplify-alone:net.tls_vs_ua_browser -> score=1 verdict=bot

One rule, one score of 1, one verdict of bot. No stacking needed, because the rule's own category is coherence and its amplified weight already reaches the top of the scale. This is the shape a team should fear most: a single misconfiguration where the network path says one operating system and the profile claims another produces a conviction from one observation.

That also explains why the browser-side coverage number matters:

coverage -> clientEvaluable=95 of 126, edge-only=31
layer:network -> rules=28 clientEvaluable=0
layer:reputation -> rules=4 clientEvaluable=0

Every network rule is edge-only, so a page that loads a detector script in a browser can never exercise the cross-layer rules that carry the highest effective weights. A team that validates a profile by loading the detector in the browser has checked 95 rules and left the 31 rules that can convict from one observation untouched. The two halves answer different questions, and a clean browser-side run is not evidence about the edge. When a header and the value a page reports disagree, the contrast compares two sources the same client produces, and client hints and the 500-character cap describes that contrast. The header surface a parser reads is one of the browser-side values a coherence rule compares, which client hints and the 500-character cap records in detail.

The last line rules out one shortcut:

order-free -> forward=0.887 reverse=0.887 same=true

The same three rules score the same in either order, because the combination is a product over weights rather than a sequence. So a report cannot tell you which rule mattered by showing the score alone, and the only way to know is to name the rules that fired, their categories and their layers. A count of findings is the least informative form of the same data.

#The two thresholds also explain why a corroborating tell is worth recording even though it cannot convict. The suspicious line at 0.35 exists so that a page which looks unusual in several independent ways surfaces for inspection, and the conviction line at 0.65 exists so that only a categorical fact turns that suspicion into a label. A team that reads the two numbers as one scale loses the distinction the ruleset is drawing: below 0.35 the page looked ordinary, between the lines it looked unusual and nothing more was claimed, and above the line one of the convicting categories fired.

That is also why the same weight means different things in different categories. A 0.6 coherence rule and a 0.6 environment rule sit at the same number and describe entirely different evidence: one says two layers of the same client disagree, the other says a surface is thinner than the ruleset's expectation. Printing the score without the category merges those two into one number that cannot be acted on, because the response to them is different: a contradiction means the deployment is inconsistent and needs the profile and the network path aligned, while a thin surface means the deployment is unusual and may be entirely legitimate.

The arithmetic itself is worth one line, because it is the reason stacking does not reach 1 by addition. The score is one minus the product of one minus each weight, so three environment tells at 0.55, 0.7 and 0.5 combine to a little over 0.93 rather than to 1.75. The formula guarantees the score stays below 1 however many corroborating tells fire, which is the mathematical reason a score alone can never outrank a category gate: it converges toward 1 without reaching it, and the label is decided by something else entirely.

Record a verdict reading

A verdict reading supports a narrow claim: this ruleset version, these fired rules, produced this score and this label. Four fields keep it checkable:

  1. The ruleset version and its digest, because a weight, a category or the convicting set can change in an update and the same signals would then score differently.
  2. The fired rules with their categories and layers, because the label depends on whether any of them may convict, and the score alone hides that.
  3. The coverage boundary, because a browser-side run never sees the edge-only rules that carry the highest effective weights.
  4. The score and the label together, because a 0.99 with corroborating tells and a 0.9 with one convicting tell are the same verdict, and a report that prints only the score implies a certainty the gate never granted.

Public sources

Practical conclusion

Read a detector's verdict by rule and category, not by score: a 0.7 from an environment rule and a 0.7 from an automation rule are the same number and completely different findings. Treat a suspicious label built only from environment tells as a description of a thin surface, and spend hardening effort on the convicting categories, where one leak decides the verdict alone. Remember that a browser-side run covers the rules a page can evaluate and leaves the cross-layer rules, whose weight is amplified to the top of the scale, entirely unseen, so a clean in-page result says nothing about the connection the client used. The scoring mechanics here sit beside the per-signal reading in native override lies and what a toString whitelist sees, and the realm consistency that prevents a coherence rule from firing is covered in browser API integrity across realms.

#Bot Detection#Cross-Layer#Verdict Scoring#Environment Signals#Fingerprinting

This article is part of the guide: What Is a Browser Fingerprint?

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.