{"id":51280749,"url":"https://github.com/okasi/detect-bot-client","last_synced_at":"2026-06-30T01:30:36.949Z","repository":{"id":365674344,"uuid":"1273201901","full_name":"okasi/detect-bot-client","owner":"okasi","description":"TypeScript bot detection: catch WebDriver, headless Chrome, Playwright/Puppeteer, robotic mouse/typing, datacenter IPs, JA3 TLS mismatch \u0026 timezone spoofing — browser + Node.","archived":false,"fork":false,"pushed_at":"2026-06-22T07:26:09.000Z","size":2036,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T07:26:16.670Z","etag":null,"topics":["anti-bot","automation-detection","behavioral-analysis","bot-detection","browser-fingerprinting","cybersecurity","fraud-protection","geoip","mouse-movement","security","tls-fingerprint"],"latest_commit_sha":null,"homepage":"https://okasi.github.io/detect-bot-client/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/okasi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-18T09:49:24.000Z","updated_at":"2026-06-22T05:51:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/okasi/detect-bot-client","commit_stats":null,"previous_names":["okasi/is-suspicious-client","okasi/anti-bot","okasi/detect-bot-client"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/okasi/detect-bot-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/okasi%2Fdetect-bot-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/okasi%2Fdetect-bot-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/okasi%2Fdetect-bot-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/okasi%2Fdetect-bot-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/okasi","download_url":"https://codeload.github.com/okasi/detect-bot-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/okasi%2Fdetect-bot-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34949234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anti-bot","automation-detection","behavioral-analysis","bot-detection","browser-fingerprinting","cybersecurity","fraud-protection","geoip","mouse-movement","security","tls-fingerprint"],"created_at":"2026-06-30T01:30:35.078Z","updated_at":"2026-06-30T01:30:36.934Z","avatar_url":"https://github.com/okasi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# detect-bot-client\n\n**Detect bots, headless browsers, and automation — in the browser and on the server.**\n\nOne library. Three layers of defense. Zero external API keys.\n\n[![npm version](https://img.shields.io/npm/v/detect-bot-client.svg)](https://www.npmjs.com/package/detect-bot-client)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://nodejs.org)\n[![CI](https://github.com/okasi/detect-bot-client/actions/workflows/ci.yml/badge.svg)](https://github.com/okasi/detect-bot-client/actions/workflows/ci.yml)\n[![IP data updates](https://github.com/okasi/detect-bot-client/actions/workflows/update-ip-data.yml/badge.svg)](https://github.com/okasi/detect-bot-client/actions/workflows/update-ip-data.yml)\n\n[Quick start](#quick-start) · [Detection modes](#detection-modes) · [Signals](#signals) · [API](#api) · [Examples](#examples) · [FAQ](#faq)\n\n\u003c/div\u003e\n\n---\n\n## Why this library?\n\nMost bot-detection snippets are copy-pasted checks that rot quickly. **detect-bot-client** gives you a maintained, typed, testable toolkit that covers the full stack:\n\n**[Live demo](https://okasi.github.io/detect-bot-client/)** — run instant and behavioral checks in your browser.\n\n| Layer | Runs where | Catches |\n|-------|------------|---------|\n| **Instant** | Browser (sync) | WebDriver, Selenium, Playwright, headless Chrome, bad WebGL/WebGPU |\n| **Behavioral** | Browser (over time) | Robotic mouse/scroll/typing, synthetic events |\n| **Server** | Node / edge | Datacenter IPs, AbuseIPDB, TLS fingerprint mismatch, timezone spoofing |\n\n- **No API keys** — GeoIP and IP blocklists are bundled and updated weekly\n- **TypeScript-first** — full types, ESM + CJS\n- **Composable** — use one layer or combine all three\n- **Explainable** — every flag has a name, weight, and confidence level\n\n---\n\n## Quick start\n\n```bash\nnpm install detect-bot-client\n```\n\n### Browser — block automation on page load\n\n```ts\nimport { detectInstantClient } from \"detect-bot-client\";\n\nconst result = detectInstantClient(window);\n\nif (!result.isLegitClient) {\n  window.location.href = \"/blocked\";\n}\n```\n\n### Server — score a request in one call\n\n```ts\nimport { detectServerClientAsync } from \"detect-bot-client\";\n\nconst result = await detectServerClientAsync({\n  clientIp: req.ip,\n  clientTimezone: req.headers[\"x-timezone\"],\n  userAgent: req.headers[\"user-agent\"],\n  tlsFingerprint: req.headers[\"x-ja3-hash\"],\n});\n\nif (!result.isLegitClient) {\n  return res.status(403).json({ signals: result.signals });\n}\n```\n\n### Behavioral — catch scripted interaction\n\n```ts\nimport { createBehavioralClientDetector } from \"detect-bot-client\";\n\nconst result = await createBehavioralClientDetector({ context: window }).observe(10_000);\n\nif (!result.isLegitClient) {\n  console.warn(\"Robotic behavior\", result.suspicionScore);\n}\n```\n\n---\n\n## Detection modes\n\n```mermaid\nflowchart LR\n  subgraph Browser\n    A[Instant] --\u003e B{Pass?}\n    B --\u003e|yes| C[Behavioral]\n    B --\u003e|no| X[Block]\n    C --\u003e D{Pass?}\n    D --\u003e|yes| E[Allow]\n    D --\u003e|no| X\n  end\n  subgraph Server\n    S[detectServerClientAsync] --\u003e T{Pass?}\n    T --\u003e|yes| E\n    T --\u003e|no| X\n  end\n  Browser --\u003e|beacon + headers| Server\n```\n\n| Mode | API | Speed | Environment |\n|------|-----|-------|-------------|\n| **Instant** | `detectInstantClient` | Immediate | Browser |\n| **Instant+** | `detectInstantClientAsync` | ~50ms | Browser (adds WebGPU check) |\n| **Behavioral** | `createBehavioralClientDetector` | 5–30s | Browser |\n| **Server** | `detectServerClientAsync` | ~1–5ms per IP | Node, edge |\n\n### Instant\n\nRuns synchronously against `window`. Async variant adds WebGPU `shader-f16` validation on Chromium.\n\n```ts\nconst sync = detectInstantClient(window);\nconst async = await detectInstantClientAsync(window);\n```\n\n### Behavioral\n\nObserves mouse, scroll, and keyboard events. Score: `1 - Π(1 - weight)` across triggered signals.\n\n```ts\nconst detector = createBehavioralClientDetector({\n  context: window,\n  scoreThreshold: 0.55,\n  onUpdate: (r) =\u003e console.log(r.suspicionScore),\n});\nawait detector.observe(8_000);\n```\n\n### Server\n\nPass `clientIp` to auto-run GeoIP lookup, datacenter range check, AbuseIPDB blocklist, iCloud Private Relay check, TLS validation, and timezone comparison.\n\n```ts\nconst result = await detectServerClientAsync({\n  clientIp: req.ip,\n  clientTimezone: req.headers[\"x-timezone\"],\n  tlsFingerprint: req.headers[\"x-ja3-hash\"],\n  userAgent: req.headers[\"user-agent\"],\n});\n```\n\nBundled IP data is refreshed weekly. Run locally: `npm run update:ip-data`.\n\n---\n\n## Signals\n\n### Instant (boolean flags)\n\nAny suspicious flag fails `isLegitClient`.\n\n| Flag | Triggers when |\n|------|---------------|\n| `isWebDriver` | `navigator.webdriver === true` |\n| `isPhantomJS` | PhantomJS globals present |\n| `isNightmare` | Nightmare.js marker |\n| `isSelenium` | Selenium document markers |\n| `isDomAutomation` | Chrome DOM automation globals |\n| `isHeadless` | WebDriver or HeadlessChrome UA |\n| `isSuspiciousResolution` | Screen \u003c 136×170 |\n| `isUserAgentValid` | UA does not start with `Mozilla/5.0 (` |\n| `isWebGLSupported` | No WebGL context |\n| `isModern` | Below Chrome 121 / Firefox 128 / Safari 16.4 |\n| `isMissingChromeObject` | Chromium without `chrome.runtime` |\n| `isSoftwareRenderer` | SwiftShader / llvmpipe WebGL |\n| `isSuspiciousWindowDimensions` | No browser chrome + origin placement |\n| `isEmptyPlugins` | Zero plugins on Chromium |\n| `isAutomationArtifacts` | ChromeDriver / Playwright markers |\n| `isSuspiciousWebDriverDescriptor` | Patched `navigator.webdriver` |\n| `isShaderF16Supported` | Async — missing WebGPU `shader-f16` on Chromium |\n\n### Behavioral (weighted)\n\n| ID | Weight | Confidence | Description |\n|----|--------|------------|-------------|\n| `no-mouse-activity` | 0.20 | low | Clicks without mouse moves |\n| `click-without-mouse-movement` | 0.35 | high | Click with no recent mouse path |\n| `linear-mouse-movement` | 0.25 | medium | Straight path, uniform speed |\n| `teleport-mouse` | 0.40 | high | Implausible cursor jumps |\n| `linear-scroll` | 0.30 | medium | Uniform scroll deltas/timing |\n| `linear-typing` | 0.35 | high | Robotic or superhuman intervals |\n| `synthetic-events` | 0.50 | high | `isTrusted === false` |\n\n### Server (weighted)\n\n| ID | Weight | Confidence | Description |\n|----|--------|------------|-------------|\n| `timezone-mismatch` | 0.50 | high | Client TZ ≠ GeoIP TZ |\n| `known-suspicious-tls` | 0.55 | high | JA3 matches Python/curl/Go/Java |\n| `tls-user-agent-mismatch` | 0.50 | high | JA3 conflicts with User-Agent |\n| `missing-tls-fingerprint` | 0.25 | medium | Browser UA without JA3 |\n| `accept-language-geo-mismatch` | 0.20 | low | Accept-Language missing GeoIP country |\n| `datacenter-browser-mismatch` | 0.35 | medium | Datacenter IP + browser UA |\n| `abuse-listed-ip` | 0.60 | high | AbuseIPDB 30-day blocklist |\n| `icloud-private-relay` | 0.15 | low | iCloud Private Relay egress |\n\n**Bundled IP data:** `data/datacenter_ip_ranges.csv` (ipcat), `data/abuse_ip_db_30d_ips.csv` (AbuseIPDB), `data/icloud_private_relay_ip_ranges.csv` (Apple).\n\n---\n\n## API\n\n```ts\nimport {\n  // Browser — instant\n  detectInstantClient,\n  detectInstantClientAsync,\n\n  // Browser — behavioral\n  createBehavioralClientDetector,\n  analyzeBehavioralSamples,\n\n  // Server\n  detectServerClient,\n  detectServerClientAsync,\n  enrichServerContext,\n  lookupClientIpGeo,\n  createIpListChecker,\n\n  // Standalone checks\n  isAutomationArtifacts,\n  isSoftwareRenderer,\n  isTimezoneMismatch,\n  isTlsUserAgentMismatch,\n  KNOWN_SUSPICIOUS_TLS_FINGERPRINTS,\n} from \"detect-bot-client\";\n```\n\n### Server options\n\n```ts\ndetectServerClientAsync(context, {\n  dataDir: \"./custom-data\",\n  lookupGeo: true,\n  checkIpLists: true,\n  timezoneToleranceMinutes: 60,\n  scoreThreshold: 0.5,\n  requireTlsFingerprint: false,\n  suspiciousTlsFingerprints: [],\n});\n```\n\n### Behavioral options\n\n```ts\ncreateBehavioralClientDetector({\n  context: window,\n  minObservationMs: 3_000,\n  scoreThreshold: 0.55,\n  pollIntervalMs: 1_000,\n  onUpdate: (result) =\u003e {},\n});\n```\n\n---\n\n## Examples\n\n### Defense in depth\n\n```ts\nconst instant = detectInstantClient(window);\nif (!instant.isLegitClient) block();\n\nfetch(\"/api/beacon\", {\n  headers: { \"X-Timezone\": Intl.DateTimeFormat().resolvedOptions().timeZone },\n});\n\nconst behavioral = await createBehavioralClientDetector({ context: window }).observe(10_000);\nif (!behavioral.isLegitClient) challenge();\n\nconst server = await detectServerClientAsync({ clientIp: req.ip /* ... */ });\nif (!server.isLegitClient) return res.status(403).end();\n```\n\n### Express middleware\n\n```ts\nimport { detectServerClientAsync } from \"detect-bot-client\";\n\napp.use(async (req, res, next) =\u003e {\n  const result = await detectServerClientAsync({\n    clientIp: req.ip,\n    clientTimezone: req.headers[\"x-timezone\"],\n    userAgent: req.headers[\"user-agent\"],\n    tlsFingerprint: req.headers[\"x-ja3-hash\"],\n  });\n\n  if (!result.isLegitClient) {\n    return res.status(403).json({ signals: result.signals });\n  }\n  next();\n});\n```\n\n### Next.js client guard\n\n```tsx\n\"use client\";\nimport { useEffect } from \"react\";\nimport { detectInstantClient } from \"detect-bot-client\";\n\nexport function BotGuard({ children }) {\n  useEffect(() =\u003e {\n    if (!detectInstantClient(window).isLegitClient) {\n      window.location.href = \"/blocked\";\n    }\n  }, []);\n  return children;\n}\n```\n\n---\n\n## FAQ\n\n**Can client-side checks be bypassed?**  \nYes. Use instant + behavioral for friction; server detection for authoritative decisions.\n\n**False positives?**  \nPossible with privacy browsers, VPNs, iCloud Relay, corporate proxies, VMs. Tune `scoreThreshold`.\n\n**How often is IP data updated?**  \nWeekly (Mondays 04:00 UTC). Run `npm run update:ip-data` locally anytime.\n\n**Works without bundlers?**  \nYes — ESM + CJS + types. Use with Vite, Webpack, Next.js, or `esm.sh`.\n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/okasi/detect-bot-client.git\ncd detect-bot-client\nnpm install\nnpx patchright install chromium   # once, for browser tests\nnpm test                          # unit tests\nnpm run test:patchright           # real Chromium via patchright\nnpm run build\nnpm run build:site          # copy browser bundle into docs/ for GitHub Pages\n```\n\nLive demo: https://okasi.github.io/detect-bot-client/ (deployed from `docs/` on push to `main`).\n\n**GitHub Pages setup (one time):** Settings → Pages → Build and deployment → **Deploy from a branch** → Branch: `gh-pages` / `/ (root)`.\n\n### Publish to npm\n\nnpm package: **`detect-bot-client`** (verified available; distinct from older [`detect-bot`](https://www.npmjs.com/package/detect-bot)).\n\n#### Step 1 — First publish (once, from your computer)\n\n```bash\ngit clone https://github.com/okasi/detect-bot-client.git\ncd detect-bot-client\nnpm install\nnpm run test\nnpm run build\nnpm login\nnpm publish --access public\n```\n\n#### Step 2 — Enable Trusted Publishing (for GitHub Actions)\n\n1. https://www.npmjs.com/package/detect-bot-client → **Settings** → **Trusted publishing**\n2. **GitHub Actions** → user `okasi`, repo `detect-bot-client`, workflow `publish.yml`\n3. Save\n\n#### Step 3 — Future releases via Actions\n\n```bash\nnpm version patch\ngit push origin main --follow-tags\n```\n\nOr re-run **Actions → Publish npm → Run workflow**.\n\nSee [AGENTS.md](AGENTS.md) for architecture and contributor guidance.\n\n## License\n\n[MIT](LICENSE) © [okasi](https://github.com/okasi)\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**If this saved you time, consider starring the repo.**\n\n[![GitHub stars](https://img.shields.io/github/stars/okasi/detect-bot-client?style=social)](https://github.com/okasi/detect-bot-client)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fokasi%2Fdetect-bot-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fokasi%2Fdetect-bot-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fokasi%2Fdetect-bot-client/lists"}