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

whichwaf

v0.2.1

Published

Which anti-bot/WAF is protecting this site — and which layer blocks you. Evidence-based detection for Cloudflare, DataDome, PerimeterX, Akamai, Kasada, Imperva, AWS WAF, Turnstile, reCAPTCHA, hCaptcha, funcaptcha.

Readme

whichwaf

Which anti-bot is protecting this site — and which layer is blocking you.

$ whichwaf https://some-protected-site.com

BLOCKED — datadome (high confidence)
  evidence: datadome interstitial + status 403

stack:
  datadome (bot-manager)
    · header: x-datadome
    · cookie: datadome
    · Behavioral scoring + device fingerprint. Session continuity matters more than raw volume.

layer: behavior
  Behavioral + device fingerprint. Session continuity (same cookies, same fingerprint,
  human cadence) beats volume.

Why

Every scraper hits a 403 and asks the same question: what am I actually up against? The existing answers are blog posts or stale signature lists. whichwaf is a CLI and library that classifies the response, shows the exact evidence that fired the verdict, and maps the block to the layer you need to fix — identity, transport, session, browser, behavior, or application.

Two design rules set it apart:

  1. Presence ≠ block. A DataDome script tag on a clean 200 is not a block. cf-ray rides on every Cloudflare-served page. whichwaf separates "this vendor is in the stack" from "this response is a challenge" — misreading the first as the second sends clean traffic to solvers and burns money.
  2. Evidence or it didn't happen. Every verdict prints the matched header, cookie, or body marker. No opaque scoring.

Install

npm

npm i -g whichwaf

Or from source:

git clone https://github.com/Oussama-Rahmouni/whichwaf.git
cd whichwaf
npm install
npm run build
npm link   # puts `whichwaf` on your PATH

Or run without linking:

npm run whichwaf -- https://example.com

Usage

whichwaf <url>              # probe a URL, print the verdict
whichwaf <url> --json       # machine-readable output
whichwaf <url> --diff       # probe as plain client AND as browser, compare verdicts
whichwaf --file urls.txt    # probe a list of URLs, aggregate summary
whichwaf --file urls.txt --report out.md   # + markdown report
whichwaf --stdin            # classify a captured raw HTTP response from stdin
whichwaf --list             # list every vendor signature

Exit codes: 0 clean · 2 block/challenge detected · 1 error — so it drops into CI and shell scripts directly.

# classify a response you captured in DevTools / mitmproxy
curl -sD - https://target.com -o /dev/null | whichwaf --stdin

Differential probing

--diff sends the same request twice — once with the honest whichwaf UA, once as current Chrome — and diffs the verdicts:

$ whichwaf https://claude.ai --diff

honest  (whichwaf UA):  BLOCKED — cloudflare (high) · status 403 · 82ms
browser (chrome UA):    BLOCKED — cloudflare (high) · status 200 · 128ms

diff: Both profiles blocked by cloudflare — the check fires regardless of client
identity. Fingerprint work alone won't help; the browser layer challenge itself
must be solved.

If the plain client is blocked but the browser profile passes, discrimination is at identity/transport (UA, TLS, header order) — that's where the fix goes. If both are blocked identically, don't waste time on fingerprints; solve the challenge.

Fleet scan

Point it at a list and get the aggregate picture — who protects what, and at which layer blocks actually happen:

whichwaf --file top-targets.txt --report report.md
scanned 5 urls in 0s
blocked: 2 · present: 0 · clean: 3 · errors: 0

vendors:
  cloudflare       4
  fastly           1

block layers:
  browser          2

The markdown report is a ready-to-publish dataset: summary, vendor distribution, layer distribution, and a per-URL details table.

Library

import { analyze, detectChallenge, fingerprintVendors, probe, diffProbe, scanUrls } from 'whichwaf';

const result = await probe('https://example.com');
const verdict = analyze(result);

verdict.blocked;            // boolean
verdict.challenge.kind;     // 'cloudflare' | 'datadome' | 'kasada' | ...
verdict.challenge.evidence; // the matched marker, for your logs
verdict.vendors;            // every vendor in the stack, block or no block
verdict.layer;              // 'identity' | 'transport' | 'session' | 'browser' | 'behavior' | 'application'
verdict.hint;               // what to fix first

const diff = await diffProbe('https://example.com');
diff.summary;               // where the stack discriminates: identity/transport vs everyone

const fleet = await scanUrls(['https://a.com', 'https://b.com']);
fleet.stats.byVendor;       // { cloudflare: 12, datadome: 3, ... }

detectChallenge({ status, headers, body }) and fingerprintVendors({ headers, body }) are pure functions — no network — if you already have the response.

Coverage

31 signatures, each with evidence-level output (whichwaf --list shows all).

Challenge-classified (block vs presence, with layer mapping): Cloudflare · DataDome · PerimeterX/HUMAN · Akamai · Kasada · Imperva/Incapsula · AWS WAF · Distil (Imperva ABP) · Queue-it · Sucuri · Wordfence · F5 BIG-IP ASM · DDoS-Guard · ModSecurity/OWASP CRS · Radware Bot Manager · Reblaze

CAPTCHA platforms: Turnstile · reCAPTCHA · hCaptcha · funcaptcha (Arkose) · GeeTest

CDN / edge fingerprinting (presence): Fastly · CloudFront · Vercel · BunnyCDN · Netlify · Alibaba (Tengine) · Azure Front Door · Google front end / Cloud Armor · Signal Sciences (Fastly Next-Gen WAF) · SafeLine

The list deliberately favors vendors a scraper actually meets over the full enterprise-appliance zoo — every signature ships with a "what this means for a scraper" note.

What it is not

This is a diagnostic tool. It does not bypass anything. Diagnosis first — bypass is a different craft, and it starts with knowing which layer fired.

License

MIT · Oussama Rahmouni