Your headers say one browser, your page says another
Your browser describes itself twice on every visit: once in the request, before the page exists, and once to the page’s own script. Untouched, the two descriptions are the same. Rewritten on one side only, they are a contradiction a check reads for free.
When your browser asks a site for a page, the request carries a few lines about the browser: the User-Agent (the browser and system name), Accept-Language (the languages you read, in order) and, in Chromium browsers, the client hints Sec-CH-UA-Platform and Sec-CH-UA-Mobile. The server has these before it sends a single byte back.
Then the page loads and its script asks the same questions again: navigator.userAgent, navigator.languages, navigator.userAgentData. In a browser nobody has changed, both answers come from the same settings, so they match exactly.
Extensions that change how a browser presents itself usually reach only one side. A user-agent switcher that patches the page’s script leaves the header alone. One that rewrites headers leaves the script alone. Either way the check now holds two descriptions of two different browsers, from one request.
Why checks read this so early
It costs them nothing. The headers arrived with the request, and the script values arrive in the first line of the challenge. Comparing them is two string comparisons, and the result is not a probability: an ordinary browser cannot produce the mismatch, so it reads as a rewritten value, which checks punish harder than anything merely unusual.
What usually causes it
- User-agent switchers. The most common cause by far. Many patch only navigator.userAgent, some only the header, a few both but not the client hints.
- Language extensions and translation tools that change the page’s language list without changing the header, or the reverse.
- Privacy extensions that “randomise” the user agent per site or per load.
- Security software and company proxies that rewrite headers on the way out. Less common, and a network matter rather than a browser one.
How to check it yourself
- Turn off every extension that mentions user agent, language, spoofing or privacy, and restart the browser.
- Take a reading on the home page. The request-headers row compares the two sides directly and names any header that disagrees.
- Turn the extensions back on one at a time and read again after each. The one that brings the row back is the cause.
If you need a different user agent
Some people switch the user agent on purpose, to get a desktop site on a tablet or to test a page. That is fine for a moment and costly as a setting you leave on, because every check you meet sees the contradiction. Turn it on for the one site that needs it and off everywhere else. A browser built to present one consistent identity, with headers and page values set together, is the only setup where the two cannot drift apart.
Request headers
What your browser sends with each request disagrees with what it tells the page.
- Turn off any user-agent switcher or language-spoofing extension, then restart the browser. These usually change one side only.
- Check the browser's language list (Settings → Languages) and put the language you actually read first.
- If security software or a company proxy rewrites your traffic, the headers can change on the way out. Test once on a home connection with that software paused.
A row's place on your reading is one multiplication, and both halves of it are published. Neither half is a measurement.
- Surprisal −log₂ of an assumed probability of 0.0100 for “request headers name a different browser, platform or language than the page reads”
- 6.64 bits
- Challenge weight Request headers disagree with the page — how much a bot check is judged to act on it
- 0.80
- Rank The product. It orders the rows and nothing else.
- 5.32
Why that assumption
An untouched browser builds its headers and its page values from the same settings, so they agree. Assumed rare because only a rewriting extension, a proxy or a script separates them.
Why that weight
The headers arrive with the request and the page values arrive with the challenge script, so a check holds both for free. A browser does not disagree with itself; an extension that rewrites one side does, and checks treat that as a rewritten value.
Every assumption, with its number → · Every weight, and what is deliberately not measured →
Questions about the “Request headers” row
- Can a website see my real user agent if I use a switcher?
- Often, yes. If the switcher changes only the page’s script, the header still carries the real value, and the server had it first. If it changes only the header, the page’s script reads the real one. Either way the site sees both, and the disagreement is the clearest signal in the request.
- Does my language setting matter to a bot check?
- Only when the two copies disagree, or when your language list points at a different country from your network and your clock. The first is this row; the second is part of the VPN row. Putting the language you actually read first in the browser’s list is the fix for both.
- Why did the captchas start after I installed a privacy extension?
- Because the extension changed what your browser says about itself, and changed it on one side only, or changes it on every load. A value that is merely unusual costs you a little. A value that contradicts itself, or changes between two requests a second apart, costs you a lot.
READ AT PRIOR TABLE ED. 1.0.0 · 2026-10-13 · ASSUMED, NOT MEASURED
The rank is taken against 15 written assumptions about how common each value is and 16 written weights about how much each one matters. 9 of them we think are probably wrong. Real counts from the browsers these sites have read appear beside a row, never inside its rank. Read the assumptions →
The rows you cannot fix from a settings page
A VPN you need for work, an address your ISP shares with a thousand other subscribers, a laptop with no graphics chip, a hardened profile that breaks the sites it was meant to protect — none of these have a checkbox. What is left is to present a reading that is internally consistent and ordinary on purpose, instead of one that is accidentally strange.
Donut Browser does that at the engine level: its Wayfern Chromium fork applies the fingerprint through CDP injection rather than patching JavaScript in the page, and it refuses to launch a profile whose proxy exit country, time zone and language contradict the profile's own fingerprint — which is the top row of most readings on this site. It is AGPL-3.0, it has no telemetry of any kind, and it runs on macOS, Windows and Linux.
Stated plainly: this site has no test results showing what any particular anti-bot service does with any particular browser, and neither does Donut Browser's own repository. What can be checked is the source, which is public.
Free · AGPL-3.0 · github.com/zhom/donutbrowser