npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hyvmind/cdp-stealth

v0.1.0

Published

Zero-dependency CDP-based browser stealth library for anti-bot evasion

Downloads

198

Readme

cdp-stealth

TypeScript Zero Dependencies License: MIT

Zero-dependency CDP-based browser stealth library for anti-bot evasion. Works with any Puppeteer variant.

Features

  • 12 stealth evasions covering all major bot detection vectors
  • Zero runtime dependencies — ships only compiled TypeScript
  • CDP-first — uses Chrome DevTools Protocol directly for User Agent + Client Hints (falls back to page API)
  • Challenge detection — detects and waits for Cloudflare, Akamai, and DataDome challenge pages
  • Fully configurable — toggle individual evasions, override UA, viewport, WebGL, hardware specs
  • Works everywherepuppeteer, puppeteer-core, @cloudflare/puppeteer

Installation

npm install @hyvmind/cdp-stealth

Quick Start

import puppeteer from "puppeteer";
import { applyStealthEvasions } from "@hyvmind/cdp-stealth";

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();

const result = await applyStealthEvasions(page);
// => { evasionsApplied: 12, appliedNames: [...], skippedNames: [], usedCDP: true }

await page.goto("https://bot.sannysoft.com");

All 12 evasions are applied by default. No configuration required.

Cloudflare Workers

cdp-stealth was built for use with @cloudflare/puppeteer in Durable Objects:

import puppeteer from "@cloudflare/puppeteer";
import { applyStealthEvasions, detectChallenge, waitForChallenge } from "@hyvmind/cdp-stealth";

export class Browser {
	private browser: puppeteer.Browser | null = null;

	async fetch(request: Request, env: Env): Promise<Response> {
		this.browser ??= await puppeteer.launch(env.MYBROWSER);
		const page = await this.browser.newPage();

		await applyStealthEvasions(page);
		await page.goto("https://example.com", { waitUntil: "networkidle0" });

		// Handle challenge pages
		if (await detectChallenge(page)) {
			const { resolved } = await waitForChallenge(page, { timeout: 15000 });
			if (!resolved) return new Response("Challenge not resolved", { status: 403 });
		}

		const content = await page.content();
		await page.close();
		return new Response(content);
	}
}

Configuration

Pass a StealthConfig object to override defaults:

import { applyStealthEvasions } from "@hyvmind/cdp-stealth";

await applyStealthEvasions(page, {
	// User agent (default: Chrome 131 on macOS)
	userAgent: {
		userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
		platform: "Win32",
		brands: [
			{ brand: "Google Chrome", version: "131" },
			{ brand: "Chromium", version: "131" },
			{ brand: "Not_A Brand", version: "24" },
		],
	},

	// Viewport (default: 1920x1080)
	viewport: { width: 1280, height: 720, deviceScaleFactor: 2 },

	// WebGL fingerprint (default: Intel Iris OpenGL Engine)
	webgl: { vendor: "NVIDIA Corporation", renderer: "NVIDIA GeForce GTX 1080" },

	// Hardware (defaults: 8 cores, 8 GB)
	hardwareConcurrency: 4,
	deviceMemory: 4,

	// Languages (default: ['en-US', 'en'])
	languages: ["fr-FR", "fr", "en"],

	// Locale and timezone
	locale: "fr-FR",
	timezone: "Europe/Paris",

	// Disable specific evasions
	evasions: {
		iframe: false,
		mediaCodecs: false,
	},
});

All fields are optional. Unset fields use sensible defaults (Chrome 131 on macOS 15).

Challenge Detection

Detect and wait for bot challenge pages (Cloudflare Turnstile, Akamai, DataDome):

import { detectChallenge, waitForChallenge } from "@hyvmind/cdp-stealth";

await page.goto("https://protected-site.com");

const isChallenge = await detectChallenge(page);

if (isChallenge) {
	const result = await waitForChallenge(page, {
		timeout: 15000, // Max wait time (default: 15s)
		pollInterval: 500, // Check interval (default: 500ms)
	});

	if (result.resolved) {
		// Challenge passed, page has navigated to real content
	} else {
		// Timeout reached, challenge still showing
	}
}

Also available as a subpath import:

import { detectChallenge, waitForChallenge } from "@hyvmind/cdp-stealth/challenge";

Evasion Reference

| # | Name | What it does | Config | | --- | --------------------- | ------------------------------------------------------------------------------------ | --------------------- | | 1 | webdriver | Removes navigator.webdriver (returns undefined, not false) | — | | 2 | userAgent | Full UA override via CDP Emulation.setUserAgentOverride with Client Hints metadata | userAgent | | 3 | chromeRuntime | Injects window.chrome stubs (runtime, app, csi, loadTimes) | — | | 4 | plugins | Realistic navigator.plugins array with proper PluginArray/MimeType prototypes | — | | 5 | languages | navigator.languages + navigator.language consistency | languages | | 6 | permissions | navigator.permissions.query() override for notifications | — | | 7 | webglVendor | WebGL UNMASKED_VENDOR/RENDERER spoofing on WebGL + WebGL2 | webgl | | 8 | outerDimensions | Fixes window.outerWidth/outerHeight/screenX/screenY (headless returns 0) | viewport | | 9 | hardwareConcurrency | navigator.hardwareConcurrency override | hardwareConcurrency | | 10 | deviceMemory | navigator.deviceMemory override | deviceMemory | | 11 | iframe | HTMLIFrameElement.contentWindow getter fix via Proxy | — | | 12 | sourceUrl | Strips //# sourceURL=pptr: from Error stack traces | — |

All evasions are enabled by default. Disable individually via evasions: { name: false }.

Builder API

StealthConfigurator provides a chainable builder for complex configurations:

import { StealthConfigurator } from "@hyvmind/cdp-stealth";

const stealth = new StealthConfigurator()
	.setUserAgent({ platform: "Win32" })
	.setViewport({ width: 1280, height: 720 })
	.setLocale("en-GB")
	.setTimezone("Europe/London")
	.setWebGL({ vendor: "NVIDIA Corporation", renderer: "NVIDIA GeForce RTX 3080" })
	.setEvasions({ iframe: false });

// Apply to multiple pages
const page1 = await browser.newPage();
await stealth.apply(page1);

const page2 = await browser.newPage();
await stealth.apply(page2);

Individual Evasions

For fine-grained control, import and apply evasions individually:

import { evasions } from "@hyvmind/cdp-stealth";

// Apply only what you need
await evasions.webdriver.apply(page);
await evasions.userAgent.apply(page, { userAgent: "Custom UA", platform: "Win32" });
await evasions.chromeRuntime.apply(page);
await evasions.plugins.apply(page);
await evasions.languages.apply(page, ["pt-BR", "pt", "en"]);
await evasions.webgl.apply(page, { vendor: "Apple", renderer: "Apple M1" });
await evasions.hardware.apply(page, 16, 32); // cores, memory GB

Or import the evasions module directly:

import * as webdriver from "@hyvmind/cdp-stealth/evasions";

API Reference

applyStealthEvasions(page, config?)

Applies all enabled stealth evasions to a page. Call once after browser.newPage().

function applyStealthEvasions(page: StealthPage, config?: StealthConfig): Promise<StealthResult>;

Returns StealthResult:

| Field | Type | Description | | ----------------- | ---------- | ------------------------------------------------ | | evasionsApplied | number | Count of evasions applied | | appliedNames | string[] | Names of applied evasions | | skippedNames | string[] | Names of skipped evasions | | usedCDP | boolean | Whether CDP session was used for UA/Client Hints |

detectChallenge(page)

Detects Cloudflare, Akamai, DataDome, and generic bot challenge pages.

function detectChallenge(page: StealthPage): Promise<boolean>;

waitForChallenge(page, options?)

Polls detectChallenge until the challenge resolves or timeout is reached.

function waitForChallenge(page: StealthPage, options?: WaitOptions): Promise<ChallengeResult>;

WaitOptions: { timeout?: number, pollInterval?: number }

ChallengeResult: { resolved: boolean, timeoutReached: boolean }

StealthConfigurator

Chainable builder. Methods: setUserAgent(), setViewport(), setLocale(), setTimezone(), setWebGL(), setEvasions(), getConfig(), apply(page).

Exported Types

import type {
	StealthPage,
	CDPSession,
	StealthConfig,
	StealthResult,
	UserAgentConfig,
	ViewportConfig,
	WebGLConfig,
	EvasionFlags,
	ChallengeResult,
	WaitOptions,
} from "@hyvmind/cdp-stealth";

How It Works

cdp-stealth uses two mechanisms to apply evasions:

  1. CDP session (page.createCDPSession()) — Used for Emulation.setUserAgentOverride which is the only way to properly set Client Hints headers (Sec-CH-UA-*). Falls back to page.setUserAgent() if CDP is unavailable.

  2. evaluateOnNewDocument() — Injects JavaScript that runs before any page scripts. This is where browser-context overrides happen (navigator properties, window dimensions, WebGL parameters, etc.). Scripts are injected once and apply to all subsequent navigations.

Each evasion module exports a NAME constant and an apply(page, ...args) function, making them composable and testable in isolation.

Compatibility

| Runtime | Package | CDP Support | | ------------------ | ----------------------- | ----------- | | Node.js | puppeteer | Full | | Node.js | puppeteer-core | Full | | Cloudflare Workers | @cloudflare/puppeteer | Full |

The library defines minimal StealthPage and CDPSession interfaces — any object matching these shapes will work, no Puppeteer type dependency required.

License

MIT