CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-puppeteer-extra-plugin-stealth

Stealth mode plugin for puppeteer-extra that applies various techniques to make detection of headless browsers harder.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

navigator-evasions.mddocs/

Navigator Evasions

Evasion techniques that modify navigator properties to hide headless browser indicators. These techniques fix various navigator object properties that can reveal automation and make the browser appear more like a regular user's browser.

Capabilities

Navigator WebDriver Evasion

Removes or fixes the navigator.webdriver property that explicitly indicates automation.

// Evasion name: 'navigator.webdriver'
// Fixes: navigator.webdriver property detection
// Launch args: Adds --disable-blink-features=AutomationControlled

This evasion:

  • Deletes the navigator.webdriver property in older Chrome versions
  • Handles different Chrome version behaviors (pre/post Chrome 88 and 89)
  • Adds --disable-blink-features=AutomationControlled launch argument
  • Prevents the most common automation detection method

Detection Method Prevented:

// This detection method will be fooled:
if (navigator.webdriver) {
  // Detects automation - webdriver property exists
} else {
  // Assumes regular browser
}

Navigator Languages Evasion

Fixes the navigator.languages array to match the Accept-Language header.

// Evasion name: 'navigator.languages'
// Fixes: Inconsistency between navigator.languages and Accept-Language header

This evasion:

  • Ensures navigator.languages matches the Accept-Language header
  • Provides consistent language reporting across different browser APIs
  • Prevents detection through language inconsistencies

Detection Method Prevented:

// This detection method will be fooled:
const languages = navigator.languages;
const acceptLanguage = /* from HTTP headers */;
if (languages.join(',') !== acceptLanguage) {
  // Detects inconsistency indicating automation
}

Navigator Hardware Concurrency Evasion

Fixes hardware concurrency reporting to match typical user systems.

// Evasion name: 'navigator.hardwareConcurrency'
// Fixes: navigator.hardwareConcurrency to report realistic CPU core count

This evasion:

  • Reports a realistic number of CPU cores
  • Prevents detection through unusual hardware concurrency values
  • Matches typical desktop/laptop configurations

Detection Method Prevented:

// This detection method will be fooled:
if (navigator.hardwareConcurrency === 1 || navigator.hardwareConcurrency > 16) {
  // Detects unusual hardware suggesting automation environment
}

Navigator Permissions Evasion

Fixes permission API behavior, particularly for notifications.

// Evasion name: 'navigator.permissions'
// Fixes: Notification.permission and navigator.permissions.query() behavior

This evasion:

  • Fixes Notification.permission behavior in headless mode
  • Ensures navigator.permissions.query() returns expected results
  • Addresses Chromium bug with notification permissions in headless mode

Detection Method Prevented:

// This detection method will be fooled:
navigator.permissions.query({name: 'notifications'}).then(result => {
  if (result.state !== Notification.permission) {
    // Detects permission API inconsistency
  }
});

Navigator Plugins Evasion

Mocks browser plugins to simulate a regular browser environment.

// Evasion name: 'navigator.plugins'
// Fixes: Empty navigator.plugins array in headless mode

This evasion:

  • Creates realistic navigator.plugins array with common plugins
  • Includes typical plugins like Chrome PDF Viewer, Flash, etc.
  • Provides proper plugin objects with realistic properties
  • Implements plugin detection methods used by websites

Detection Method Prevented:

// This detection method will be fooled:
if (navigator.plugins.length === 0) {
  // Detects headless browser with no plugins
} else {
  // Assumes regular browser with plugins
}

Navigator Vendor Evasion

Allows customization of the navigator.vendor property which is fixed in Puppeteer by default.

/**
 * Navigator vendor evasion plugin
 * @param opts - Configuration options
 * @param opts.vendor - The vendor string to use (default: 'Google Inc.')
 * @returns Plugin instance
 */
function NavigatorVendorPlugin(opts?: {
  vendor?: string;
}): Plugin;

// Evasion name: 'navigator.vendor'
// Fixes: Fixed navigator.vendor property in headless mode
// Configuration: Accepts custom vendor string

This evasion:

  • Overrides the fixed navigator.vendor property
  • Allows setting custom vendor string (default: 'Google Inc.')
  • Uses stealth utilities to replace the property undetectably
  • Can be used to mimic different browser vendors

Configuration Options:

// Use default vendor (Google Inc.)
const vendorPlugin = NavigatorVendorPlugin();

// Use custom vendor
const appleVendor = NavigatorVendorPlugin({ 
  vendor: 'Apple Computer, Inc.' 
});

Detection Method Prevented:

// This detection method will be fooled:
if (navigator.vendor === 'Google Inc.' && /* other headless indicators */) {
  // Might detect specific headless patterns
} else {
  // Custom vendor helps avoid detection patterns
}

Usage Examples:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

// Enable only navigator-related evasions
const navigatorStealth = StealthPlugin({
  enabledEvasions: new Set([
    'navigator.webdriver',
    'navigator.languages',  
    'navigator.hardwareConcurrency',
    'navigator.permissions',
    'navigator.plugins',
    'navigator.vendor'
  ])
});

puppeteer.use(navigatorStealth);

const browser = await puppeteer.launch();
const page = await browser.newPage();

// Navigator properties will now appear normal
await page.evaluate(() => {
  console.log(navigator.webdriver); // undefined (not true)
  console.log(navigator.languages); // ['en-US', 'en'] 
  console.log(navigator.hardwareConcurrency); // 4 (realistic number)
  console.log(navigator.plugins.length); // > 0 (has plugins)
  console.log(navigator.vendor); // 'Google Inc.' (customizable)
  
  // Permissions work normally
  return navigator.permissions.query({name: 'notifications'});
});

Configuration Interaction

Navigator evasions work together with other plugin components:

User Agent Override Integration

The navigator.languages evasion coordinates with user-agent-override to ensure:

  • Accept-Language header matches navigator.languages
  • User agent string is consistent with reported languages
  • Platform information aligns across different APIs

Launch Arguments

The navigator.webdriver evasion adds launch arguments:

  • --disable-blink-features=AutomationControlled
  • May add Chrome policy warning (can be disabled with system policies)

Implementation Details

Each navigator evasion:

  1. Runtime Injection: Uses page.evaluateOnNewDocument() for immediate execution
  2. Property Modification: Uses stealth utilities to modify properties undetectably
  3. Cross-Frame Support: Works across iframes and new windows
  4. Version Compatibility: Handles different Chrome/Chromium versions appropriately
  5. Realistic Data: Provides believable values that match real user environments

Browser Compatibility

Navigator evasions are designed for:

  • Chrome/Chromium: Primary target with version-specific handling
  • Puppeteer: Full compatibility with all Puppeteer versions
  • Playwright: Compatible with playwright-extra
  • Cross-Platform: Works on Windows, macOS, and Linux

Advanced Usage

Custom Plugin Configuration

// Enable specific navigator evasions with custom values
const customNavigator = StealthPlugin({
  enabledEvasions: new Set(['navigator.webdriver', 'navigator.plugins', 'navigator.vendor'])
});

// For individual evasion plugins with custom options:
const customVendor = require('puppeteer-extra-plugin-stealth/evasions/navigator.vendor');
puppeteer.use(customVendor({ vendor: 'Apple Computer, Inc.' }));

Debugging Navigator Properties

// Check which navigator properties are being modified
await page.evaluate(() => {
  const props = [
    'webdriver', 'languages', 'hardwareConcurrency', 
    'permissions', 'plugins', 'vendor'
  ];
  
  return props.map(prop => ({
    property: prop,
    value: navigator[prop],
    type: typeof navigator[prop]
  }));
});

Install with Tessl CLI

npx tessl i tessl/npm-puppeteer-extra-plugin-stealth

docs

chrome-evasions.md

core-plugin.md

fingerprinting-evasions.md

index.md

misc-evasions.md

navigator-evasions.md

window-frame-evasions.md

tile.json