Skip to content
All fingerprint checksEnvironment & locale

navigator.plugins is consistent with the PDF viewer

Check id has-plugins

What an ordinary browser yields

pdfViewerEnabled=true together with a non-zero plugin count (real Chrome exposes several PDF-viewer entries), or pdfViewerEnabled=false with an empty list.

What a detector infers

An empty `navigator.plugins` is only meaningful when it contradicts something else, and this check is written to respect that. It reads `navigator.pdfViewerEnabled` first: only when that is true does it require `navigator.plugins.length > 0`. Per spec, Chrome exposes its PDF-viewer plugin entries exactly when the PDF viewer is supported, so 'pdfViewerEnabled=true with 0 plugins' is a state real Chrome cannot produce — it means someone reported the flag without materialising the plugin array behind it. When pdfViewerEnabled is false or absent, the check returns N/A rather than failing, because mobile Chrome, iOS/WebKit and policy-disabled desktop all legitimately report no viewer and no plugins.

How to resolve it

Make the two agree at the source. If you are presenting desktop Chrome, run a build with the PDF viewer enabled so the plugin entries exist naturally, rather than setting pdfViewerEnabled or the plugin list independently from JS.

How is a headless browser detected?

Read in the wild by

Kasada

Reads the plugin list in both the page realm and a fresh iframe realm, filling separate payload slots for each.

427-probe slot map: 7:navigator_plugins(fn_235083), 248:window_plugins_list(fn_26695), 280:window_result_plugins(fn_134365), 49:iframe_plugins_list(fn_173458), 166:iframe_result_plugins(fn_182879)

PerimeterX / HUMAN

Walks navigator.plugins and ships the plugin name list verbatim (unhashed), capped at 30 entries, alongside the plugin count.

Deployed PerimeterX collector main.min.js (ifood; PerimeterX_RE/stample/ifood/source/main.min.js, mirrored at node_bridge/ifood/perimeterx/main.min.js). Collector `CC()` walks the plugin list: `for(hM[hR+-100]=0;hM[hT+260]<hU.plugins[hQ(155)]&&hM[hR+-100]<BO;hM[1]++)hM[204][hQ(hT+493)](hU[hQ(382)][hM[1]].name)` — with `BO=30`, `hU=navigator` (the file's only navigator binding), and string-table entries `hQ(382)='plugins'`, `hQ(155)='length'` (hQ_map.json). Callsite `Cq()` closes the chain: `hU[hQ(382)]&&(t[2]=hU[hQ(382)][hQ(155)],t[3]=CC()),t[0]["T386NQoaPg4="]=t[3],t[0][hQ(839)]=t[2]` — the name list goes to EV2 field `T386NQoaPg4=` and the count to the field written via hQ(839). Decoded payload (all_fields_map.json line 949, case 2026-05-19_ifood): key `T386NQoaPg4=`, via "plain", val `["PDF Viewer","Chrome PDF Viewer","Chrom…`; the count field carries val "5", matching Chrome's five PDF-viewer plugins.

CreepJS

CreepJS, an open-source fingerprinting research demo rather than a production detector, counts an empty `navigator.plugins` on a Blink runtime among its "like headless" indicators, alongside an empty `navigator.mimeTypes` list.

src/headless/index.ts, `likeHeadless` block: `noPlugins: IS_BLINK && navigator.plugins.length === 0`, `noMimeTypes: IS_BLINK && mimeTypes.length === 0` (where `mimeTypes = Object.keys({ ...navigator.mimeTypes })`)

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 has-plugins belongs to.