Can a website tell an AI agent from a person?
10 automation stacks actually launched against a live page — not a list of tips.
AI agents are starting to browse, compare and buy on your pages. Whether you want to welcome them or keep them out, the first question is the same: can your site even see them? We launched real automated browsers — plain headless, headed, and progressively better stealth patches — against a live page and recorded, for each one, which detection checks fired. Read the ladder, not the last cell: every check a hiding stack defeats costs its author another specific fix, in every session and on every site.
What do AI agents get from your pages?
Free scan: what ChatGPT, Claude and Perplexity read on your site, and what to fix.
The stacks, and what gave them away
Each row is a browser actually launched against the page. The badges are the checks that fired on it.
- Playwright, default headless (chromium headless shell)Detected
5 in-page checks fired · 2 header checks fired
navigator.webdriver is true"Headless" in the User-Agent stringWebGL renderer is a software rasterizerZero browser plugins and MIME typesNo real interaction observed on the page"Headless" in the User-Agent headerNo Accept-Language header sent - Playwright, full Chromium in headless modeDetected
4 in-page checks fired · 1 header checks fired
navigator.webdriver is true"Headless" in the User-Agent stringWebGL renderer is a software rasterizerNo real interaction observed on the page"Headless" in the User-Agent header - Playwright, headed Chromium on a virtual displayDetected
2 in-page checks fired · 0 header checks fired
navigator.webdriver is trueWebGL renderer is a software rasterizer - Headless shell + the public stealth patch (spoofs webdriver, plugins, languages, cores, window size, permissions, WebGL vendor) and a real-browser User-AgentDetected
6 in-page checks fired · 2 header checks fired
The webdriver flag has been rewrittennavigator.plugins is not a real PluginArrayNative browser functions have been replacedSpoofed properties answer to an object that is not a browserUser-Agent string disagrees with Client HintsNo real interaction observed on the pageNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - Headless shell + a stealth patch written AGAINST these checks (flag moved to the prototype, real PluginArray of real Plugin objects, every patched function reporting itself as native, UA client hints aligned)Detected
2 in-page checks fired · 2 header checks fired
Spoofed properties answer to an object that is not a browserNo real interaction observed on the pageNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - Headless shell + the same counter-patch, plus a brand check inside every fake getter (it refuses an object that is not a browser, exactly as the engine does)Detected
2 in-page checks fired · 2 header checks fired
The refusal itself is acted out by page codeNo real interaction observed on the pageNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - Headless shell + the same brand-checked counter-patch, plus stack hiding (Error.captureStackTrace strips the patch’s own frames, so the acted refusal looks like the engine’s)Undetected
1 in-page checks fired · 2 header checks fired
No real interaction observed on the pageNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - Headless shell + the same stack-hiding counter-patch, but installed in the top frame only (a patch that does not think about frames)Detected
2 in-page checks fired · 2 header checks fired
A fresh realm disagrees with this oneNo real interaction observed on the pageNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - The best evasion measured here (stack-hiding counter-patch, installed in every frame — the only row that comes out clean), plus the driver moving the mouse across the page: the answer to "automation never touches anything"Undetected
1 in-page checks fired · 2 header checks fired
Pointer moved in a perfectly even progressionNo Accept-Language header sentSec-CH-UA version disagrees with the User-Agent - Not a browser at all: a raw HTTP client (Node http.request) that copies every Chrome header value — User-Agent, Sec-CH-UA, Accept-Language, sec-fetch-* — but not the order a real Chromium network stack emits them inUndetected
0 in-page checks fired · 1 header checks fired
Headers arrive in an order no Chromium emits
Which checks earn their keep
How many stacks each check catches, counted only on the rows where it can be judged.
| Check | Layer | Stacks caught |
|---|---|---|
| No real interaction observed on the page | In page | 7 of 9 |
| No Accept-Language header sent | HTTP headers | 7 of 10 |
| Sec-CH-UA version disagrees with the User-Agent | HTTP headers | 6 of 10 |
| navigator.webdriver is true | In page | 3 of 9 |
| WebGL renderer is a software rasterizer | In page | 3 of 9 |
| Spoofed properties answer to an object that is not a browser | In page | 2 of 9 |
| "Headless" in the User-Agent string | In page | 2 of 9 |
| "Headless" in the User-Agent header | HTTP headers | 2 of 10 |
| Native browser functions have been replaced | In page | 1 of 9 |
| navigator.plugins is not a real PluginArray | In page | 1 of 8 |
| Zero browser plugins and MIME types | In page | 1 of 9 |
| Pointer moved in a perfectly even progression | In page | 1 of 1 |
| A fresh realm disagrees with this one | In page | 1 of 9 |
| The refusal itself is acted out by page code | In page | 1 of 8 |
| User-Agent string disagrees with Client Hints | In page | 1 of 9 |
| The webdriver flag has been rewritten | In page | 1 of 9 |
| Headers arrive in an order no Chromium emits | HTTP headers | 1 of 10 |
| Known Selenium / ChromeDriver / PhantomJS markers | In page | 0 of 9 |
| navigator.hardwareConcurrency is zero | In page | 0 of 9 |
| Empty navigator.languages | In page | 0 of 9 |
| Window outerWidth/outerHeight is zero | In page | 0 of 9 |
| Permissions API vs Notification.permission mismatch | In page | 0 of 9 |
| No sec-fetch-* headers sent | HTTP headers | 0 of 10 |
| Header User-Agent differs from the in-page one | HTTP headers | 0 of 10 |
Live census
112 sessions have run the self-check in their own browser and posted back an anonymous verdict — never a fingerprint. Share of those sessions where each signal fired:
| Check | Share of sessions |
|---|---|
| Zero browser plugins and MIME types | 95% |
| "Headless" in the User-Agent string | 92% |
| navigator.webdriver is true | 90% |
| WebGL renderer is a software rasterizer | 90% |
| No real interaction observed on the page | 17% |
| Pointer moved in a perfectly even progression | 5% |
| User-Agent string disagrees with Client Hints | 2% |
| The webdriver flag has been rewritten | 1% |
| Native browser functions have been replaced | 1% |
Most sessions so far come from our own bench and from crawlers, not from a third-party audience: read it as a catalogue of signals, not as a measurement of the web.
Chromium-family browsers only (what the measuring machine has installed) and no GPU on it, so the software-renderer check fires on every row — there it is a property of the machine, not of automation. Last measured: 2026-09-20.