Skip to content
All fingerprint checksEnvironment & locale

navigator.plugins is a real platform object (structured-clone probe)

Check id plugins-structured-clone

What an ordinary browser yields

structuredClone(navigator.plugins) throws a DataCloneError.

What a detector infers

The structured-clone algorithm is implemented in C++ and interrogates objects about what they actually are, not what they claim. A genuine PluginArray is a non-serializable platform object, so structuredClone(navigator.plugins) must throw — and specifically must throw a DataCloneError. This check first confirms the gate (structuredClone exists and Object.prototype.toString.call(navigator.plugins) reports [object PluginArray]), reporting N/A otherwise, then requires that a throw occurs and that the error's name is DataCloneError. Only the error name is asserted, never its message, because wording varies by engine and entry point. A plain Array or object impersonating a PluginArray clones cleanly and silently succeeds — no JS construct can fabricate non-serializability — so a successful clone lets a detector infer the plugin list is a fake that merely carries the right toStringTag, and a differently-named error indicates a JS throw standing in for the serializer's.

How to resolve it

Don't synthesize navigator.plugins from a JS array or object. The serializer's refusal is a hard C++ invariant that a JS replacement cannot reproduce — matching Symbol.toStringTag gets past shallow type checks but not this one. If the plugin list needs to differ, change it at the engine level so the object remains a real PluginArray, or leave the native list intact.

What is headless browser detection?

Read in the wild by

CreepJS

CreepJS, a research demo rather than a production detector, flags a fabricated plugin list by inspecting the entries themselves: it requires each plugin's first item to have a prototype whose constructor is named "MimeType", recording a "missing mimetype" lie against Navigator.plugins when it isn't — and treating a throw there as a sign of tampering. It reaches the same signal this check probes, a plugins list synthesized in JS, by prototype inspection rather than by structured clone.

src/lies/index.ts:870-884 (getPluginLies): `// 3. Expect MimeType object in plugins` … `const validMimeType = Object.getPrototypeOf(plugin[0]).constructor.name == 'MimeType'` … `lies.push('missing mimetype')`, with `catch { … return true // sign of tampering }`; recorded as a lie via src/navigator/index.ts:253-258: `const { lies } = getPluginLies(plugins, navigator.mimeTypes)` → `lied = true` → `documentLie('Navigator.plugins', lie)`.

Every attribution traces to a published artifact. See the sources and their limits.

Nearby checks in Environment & locale

See all 23 checks in Environment & locale
Who builds this test

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 Environment & locale 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 Environment & locale — the family plugins-structured-clone belongs to.