← Glossary / Plugin Enumeration

What is Plugin Enumeration?

Plugin enumeration is a client-side fingerprinting technique where anti-bot scripts iterate through the navigator.plugins and navigator.mimeTypes arrays to verify browser authenticity. Because default headless browsers ship with an empty plugin array, it serves as a high-confidence, zero-latency tripwire for bot detection. For scraping pipelines, failing to present a credible, OS-appropriate plugin list guarantees a block from vendors like Cloudflare and DataDome before the first DOM interaction.

FingerprintingHeadless DetectionNavigator APIJS ChallengePlaywright
// 02 — definitions

Count the
plugins.

How anti-bot scripts use a legacy browser API to instantly separate real human traffic from default headless automation.

Ask a DataFlirt engineer →

TL;DR

Plugin enumeration checks the length, names, and prototype chains of installed browser plugins. Real Chrome on macOS has a specific, hardcoded list of PDF viewers. Headless Chrome has none. It is a binary check that catches 90% of naive scraping scripts instantly, and poorly spoofed arrays catch the remaining 9%.

01Definition & structure
Plugin enumeration relies on two interconnected browser properties: navigator.plugins and navigator.mimeTypes. The plugins array contains a list of Plugin objects, each detailing its name, description, and supported file types. The mimeTypes array contains MimeType objects, which hold a circular reference back to the Plugin that enables them. Anti-bot scripts read these arrays, hash the contents, and validate the structural integrity of the objects to determine if the browser is a real consumer client or a headless automation tool.
02How it works in practice
When a scraper hits a protected endpoint, the server returns a lightweight JavaScript challenge before serving the actual HTML. This script executes immediately, iterating over navigator.plugins. If the length is zero (the default for Puppeteer and Playwright), the script immediately flags the session. If plugins are present, it performs deep introspection—checking prototype chains, evaluating toString() outputs, and ensuring the circular references between plugins and mimeTypes are intact. The results are hashed and sent back to the edge server, which decides whether to issue a block, a CAPTCHA, or a 200 OK.
03The stealth plugin trap
Many developers attempt to bypass enumeration using tools like puppeteer-extra-stealth. These tools inject JavaScript into the page before it loads, overriding the navigator.plugins getter to return a fake array of objects. However, native browser objects behave differently than JavaScript objects. For example, calling Object.getOwnPropertyNames() on a native PluginArray yields different results than calling it on a JS Proxy. Anti-bot vendors actively profile these specific spoofing libraries; using them often results in a faster, more definitive block than simply running a naked headless browser.
04How DataFlirt handles it
We do not play the JavaScript spoofing game. DataFlirt bypasses plugin enumeration by running actual, headed browser binaries inside virtualized framebuffers (Xvfb/Wayland) on our worker nodes. Because the browser is genuinely rendering a headed environment, the C++ engine naturally exposes the exact, unmodified plugin memory structures. There are no proxies, no overridden getters, and no prototype leaks. The anti-bot script inspects the array, finds a structurally perfect, native implementation, and passes the session.
05Did you know?
Historically, plugin enumeration was used to track users across the web. Because users had unique combinations of Flash, Java, Silverlight, and PDF viewers installed, the plugin array provided high entropy for stateless tracking. To combat this privacy leak, Google hardcoded the plugin list in Chrome 94. Today, every Chrome user on a given OS presents the exact same plugins. While this killed plugins as a tracking vector, it cemented them as the ultimate binary test for headless browser detection.
// 03 — the validation math

How scripts verify
plugin authenticity.

Anti-bot vendors don't just check if plugins exist. They validate the memory structures, prototype chains, and cross-references between the plugins and mimeTypes arrays.

Headless heuristic = navigator.plugins.length === 0
The simplest check. True in default Puppeteer/Playwright. Instant block. Standard WAF JS Challenge
Prototype integrity = plugins[0] instanceof Plugin
Fails if the array is mocked using standard JavaScript objects or Proxies. DataDome client-side probe
MimeType linkage = plugins[0][0].enabledPlugin === plugins[0]
Circular reference check. Naive spoofing scripts almost always miss this. Akamai BMP telemetry
// 04 — client-side execution

A plugin probe
in real time.

Trace of an anti-bot JavaScript challenge evaluating the navigator object of a scraper using a basic stealth plugin. The spoofing is detected via prototype leakage.

JS ChallengePrototype ChainDataDome
edge.dataflirt.io — live
CAPTURED
// executing client-side probe
probe.plugins.length: 5 // looks normal
probe.plugins[0].name: "Chrome PDF Plugin"

// deep validation
Object.getPrototypeOf(plugins): PluginArray.prototype // pass
plugins[0] instanceof Plugin: false // FLAG: Object is a Proxy
plugins[0].toString(): "[object Object]" // FLAG: Expected [object Plugin]

// mime-type cross-reference
mimeTypes["application/pdf"].enabledPlugin: undefined // FLAG: Linkage broken

// payload generation
fp.plugin_hash: "mocked_proxy_detected"
classifier.bot_probability: 0.99
action: BLOCK_AND_TARPIT
// 05 — detection vectors

Where spoofing
scripts fail.

Mocking the plugins array in JavaScript is notoriously difficult because the real API is implemented in C++ and has unique memory behaviors. These are the most common failure points for scrapers.

SPOOFING ATTEMPTS ·  ·    1.2M analyzed
DETECTION RATE ·  ·  ·    91.4%
UPDATED ·  ·  ·  ·  ·  ·  2026-05-19
01

Empty array (default headless)

binary · No spoofing attempted; instant failure
02

Prototype chain mismatch

high · Using JS objects instead of native Plugin instances
03

Missing MimeType circular links

high · Failing to cross-reference mimeTypes to plugins
04

Function toString() leakage

med · Overridden getters returning JS source instead of native code
05

OS/Browser version mismatch

med · Presenting Windows plugins on a macOS User-Agent
// 06 — our approach

Don't mock the array,

run the real engine.

Spoofing navigator.plugins via JavaScript getters is a losing game. Modern anti-bot scripts check Object.getOwnPropertyDescriptor and evaluate the string representations of the spoofed functions. DataFlirt bypasses enumeration entirely by running actual headed browser builds in virtualized framebuffers. The C++ engine naturally exposes the exact plugin memory structures, prototype chains, and circular references the JS engine expects, because they are real.

plugin.validation.trace

Live telemetry from a DataFlirt worker passing a strict plugin enumeration challenge.

browser.engine Chrome 124.0.6367.60
plugins.length 5standard
prototype.integrity native C++ bindingpass
mimetype.linkage circular ref intact
tostring.validation [native code]pass
os.coherence macOS PDF Viewermatches UA
challenge.result 0.01 · human

Stay ahead of the pipeline

Data engineering
intel, weekly.

Anti-bot shifts, scraping infrastructure updates, dataset delivery patterns, and business outcomes from our pipelines. Short, technical, no fluff.

// 07 — FAQ

Common
questions.

Common questions about plugin enumeration, headless browser detection, and why legacy APIs still matter for modern scraping.

Ask us directly →
What exactly is the navigator.plugins array? +
It is a legacy Web API that returns an array of plugins installed in the browser, such as PDF viewers or (historically) Flash and Java. While modern browsers have deprecated third-party plugins, they still hardcode a list of default internal plugins (like the Chrome PDF Viewer) to maintain web compatibility. Anti-bot scripts use this hardcoded list as a baseline for human traffic.
Why does headless Chrome have an empty plugins array? +
Headless Chrome is optimized for automated testing and CI/CD environments. To save memory and execution time, the Chromium team strips out non-essential consumer features, including the PDF viewer and the associated plugin bindings. This optimization inadvertently created the most reliable headless detection vector in existence.
Can I just mock navigator.plugins using JavaScript? +
You can, but it rarely works against enterprise anti-bot systems. When you mock the array using JavaScript Proxy objects or Object.defineProperty, you leave traces in the prototype chain. Advanced scripts check if plugins instanceof PluginArray and verify that the internal functions return [native code] when cast to a string. JS mocks fail these deep inspections.
How did Chrome 94 change plugin fingerprinting? +
In Chrome 94, Google standardized the navigator.plugins array. Instead of returning the actual plugins installed on the host OS, Chrome now returns a fixed, hardcoded list of standard PDF viewers for all users. This reduced the entropy (uniqueness) of the fingerprint, but made headless detection even more binary: you either have the exact hardcoded list, or you are a bot.
Does puppeteer-extra-stealth solve plugin enumeration? +
It attempts to, but its implementation is widely fingerprinted. The stealth plugin injects a JavaScript mock of the plugins array. While it passes basic length checks, vendors like Cloudflare and DataDome have specifically profiled the exact prototype leaks and memory structures generated by puppeteer-extra-stealth. Using it often results in a higher bot score than using nothing at all.
How does DataFlirt handle plugin enumeration at scale? +
We do not use JavaScript injection to fake plugins. Our infrastructure runs modified, headed browser binaries inside virtualized X11/Wayland framebuffers. Because the browser engine is genuinely rendering a headed environment, the C++ bindings for the plugin array are naturally present and structurally perfect. It passes 100% of prototype and linkage checks because it is not a spoof.
$ dataflirt scope --new-project --target=plugin-enumeration READY

Tell us what
to extract.
We do the rest.

20-minute scoping call. Pilot dataset within the week. Production within two. Whether you need a one-off catalogue dump or a continuous feed across millions of records — we scope, build, and operate the pipeline.

hello@dataflirt.com  ·  Bengaluru  ·  IST  ·  typical reply < 4h