No console preview enumerated a prototype-chain Proxy
Check id console-preview-proxy-trap
What an ordinary browser yields
Nothing. On a browser with no Runtime client attached, a console argument is never previewed, the prototype is never enumerated, and the trap never fires.
What a detector infers
DevTools has to look inside an object to show it to you, and in JavaScript looking inside an object is an observable act. That tension is the whole signal. When the CDP Runtime domain is enabled — by an open DevTools window, or by Puppeteer, Playwright or anything else calling Runtime.enable — V8's inspector intercepts every console.* call and hands each argument to V8ConsoleMessage::wrapArguments with generatePreview set, so the panel can render an expandable view of it. Building that preview means enumerating the object's keys. Enumerating a plain object is a silent memory read; enumerating a Proxy is a function call, because the specification gives an engine no way to obtain a Proxy's own keys without invoking its ownKeys trap. So the page supplies the trap and learns, synchronously and deterministically, that something serialised its argument. The construction that reaches the trap is the interesting part, because Chromium's developers anticipated the direct attack and guarded it twice: buildObjectPreviewInternal unwraps a Proxy argument down to its target before previewing, and DebugPropertyIterator::Create skips past a Proxy receiver before collecting keys. Both guards test only the immediate value. The bait is therefore not a Proxy at all — it is a plain object created with a Proxy as its PROTOTYPE, so typeof reads "object", both guards pass on the surface, and the iterator then walks the prototype chain eagerly at construction time and reaches the Proxy regardless. This is contextual and never scored, and the reason is the same one that keeps every DevTools row unscored here: a person with their own DevTools open trips it exactly as an automation framework does, and from V8's side the two are the same code path. What it is good for is the thing the older Error.stack probe can no longer do — that vector was closed in V8 in May 2025, and this one was still open when it was published in March 2026.
How to resolve it
There is nothing to fix for a person browsing with DevTools open — the row is reporting a fact about your session, not a fault in your browser. If you are driving the page, the signal comes from the Runtime domain being enabled, so it is not addressable by patching anything in the page: an override in the main world runs long after the C++ inspector has already called the trap. Detach Runtime.enable when the run does not need it, or use a driver that keeps its evaluation in an isolated world and leaves the domain off. Note that suppressing the specific trap — refusing to preview objects with a Proxy in the chain — would be the same mistake this audit documents everywhere else: it patches the probe rather than the condition, and the condition is that a debugger is attached.
Read in the wild by
Sveba (research)
Publishes the technique and traces it through V8's own sources — the preview call in v8-console-message.cc, the surface-only Proxy guards in value-mirror.cc and debug-property-iterator.cc, and the spec-mandated trap invocation in keys.cc — with logs from an instrumented content_shell build.
console.groupEnd(Object.create(new Proxy({}, { ownKeys(){...} })))
How V8 Leaks Your Headless Browser's IdentityRebrowser
Documents the older, now-patched half of the same question: the Error.stack accessor read on the inspector's error-formatting path, which this row exists to outlive.
index.js runtimeEnableTest
Rebrowser bot detectorEvery attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Automation surface
- navigator.webdriver does not assert WebDriver control
webdriver-offThis check is intentionally one-way. The WebDriver specification defines navigator.webdriver as the browser's assertion that it is under… - navigator.webdriver is a native prototype getter returning false
webdriver-descriptor-integrityRather than reading the value, this check inspects the property's shape: it requires a getter on Navigator.prototype whose… - no Selenium / Puppeteer / Playwright globals
automation-globalsThis check tests a fixed list of roughly two dozen known names against both window and document (Selenium's __webdriver_evaluate /… - DevTools Runtime serialization is not active
runtime-enable-leakAn attached DevTools Runtime domain serialises console arguments and reads an Error stack accessor. - No Puppeteer / Playwright sourceURL marker
sourceurl-leakPuppeteer and Playwright evaluation labels can appear in main-world Error stacks. - No controller code crossed the main-world canary
main-world-executionAn early DOM canary records main-world calls, but page code and extensions can also call it, so this is contextual. - No exposed Puppeteer / Playwright binding
exposed-binding-leaksExposed functions leave Playwright/Puppeteer registries, prefixes, source text, or __installed markers. - Content Security Policy is enforced
csp-bypassThe audit excludes data: scripts. Execution despite script-src is direct CSP-bypass behavior.
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Automation surface checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Automation surface — the family console-preview-proxy-trap belongs to.