{"id":50891506,"url":"https://github.com/acunningham-ship-it/veilbrowser","last_synced_at":"2026-06-15T21:03:18.122Z","repository":{"id":363579324,"uuid":"1263961048","full_name":"acunningham-ship-it/veilbrowser","owner":"acunningham-ship-it","description":"Stealth browser for AI agents — real Chrome over raw CDP, no Playwright/Puppeteer. TypeScript + MCP-native. Passes sannysoft 57/57, bypasses Cloudflare.","archived":false,"fork":false,"pushed_at":"2026-06-09T13:25:02.000Z","size":555,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T14:22:18.002Z","etag":null,"topics":["ai-agents","anti-bot","browser-automation","cdp","chrome","mcp","nodriver","playwright-alternative","puppeteer-alternative","stealth-browser","typescript","web-scraping"],"latest_commit_sha":null,"homepage":null,"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/acunningham-ship-it.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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":null,"dco":null,"cla":null}},"created_at":"2026-06-09T12:33:44.000Z","updated_at":"2026-06-09T13:45:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/acunningham-ship-it/veilbrowser","commit_stats":null,"previous_names":["acunningham-ship-it/veilbrowser"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/acunningham-ship-it/veilbrowser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acunningham-ship-it%2Fveilbrowser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acunningham-ship-it%2Fveilbrowser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acunningham-ship-it%2Fveilbrowser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acunningham-ship-it%2Fveilbrowser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acunningham-ship-it","download_url":"https://codeload.github.com/acunningham-ship-it/veilbrowser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acunningham-ship-it%2Fveilbrowser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34379915,"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-15T02:00:07.085Z","response_time":63,"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":["ai-agents","anti-bot","browser-automation","cdp","chrome","mcp","nodriver","playwright-alternative","puppeteer-alternative","stealth-browser","typescript","web-scraping"],"created_at":"2026-06-15T21:03:17.351Z","updated_at":"2026-06-15T21:03:18.112Z","avatar_url":"https://github.com/acunningham-ship-it.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Veil\n\n**A stealth automation runtime for AI agents. Drives real Chrome over raw CDP — no Playwright, no Puppeteer, no WebDriver, zero runtime dependencies.**\n\n![Veil passing bot.sannysoft.com and Cloudflare's challenge](assets/demo.gif)\n\n*Veil driving real Chrome: bot.sannysoft.com all-green, then straight through Cloudflare's JS challenge — no patches, no plugins.*\n\nTo Instagram, Google, Reddit, Datadome, Akamai — Veil *is* Chrome. Same binary, same\nTLS, same JS engine, same canvas/WebGL/font fingerprint a human's browser has. We\ndon't reimplement the browser (that's Chromium's 20-year, 1000-engineer job, and a\nhand-rolled engine is *easier* to fingerprint, not harder). We replace the part that\ngets you caught: **the automation layer.**\n\n---\n\n## Why not Playwright / Puppeteer?\n\nThey're powerful and great for scripted QA. For *agents on hostile sites* they have three structural problems:\n\n| Problem | Playwright/Puppeteer | Veil |\n|---|---|---|\n| **Detectable** | `navigator.webdriver=true`, `--enable-automation`, the `Runtime.enable` CDP tell, `HeadlessChrome` UA | webdriver scrubbed, no automation switches, no `Runtime.enable`, UA + client-hints normalized |\n| **Robotic input** | instant teleport clicks, fixed-cadence typing → behavioural detection | curved Bézier mouse paths, eased timing, human keystroke cadence |\n| **Brittle for agents** | CSS/XPath selectors that break constantly | accessibility-tree snapshot → stable integer `ref`s; agents never write a selector |\n\nVeil is **dependency-free** — Node 24 / Bun ship a global `WebSocket`, so the entire\nCDP transport is ~120 lines we own. Nothing to patch, nothing to leak.\n\n## How Veil compares (honest)\n\nThe \"real Chrome over raw CDP\" idea isn't new — Python's [`nodriver`](https://github.com/ultrafunkamsterdam/nodriver)\npioneered it, and [Camoufox](https://camoufox.com) (a C++-patched Firefox) scores even\nbetter on pure stealth. Veil isn't claiming to out-stealth them. Its wedge is **where it\nlives and how agents use it**:\n\n- **TypeScript-native.** The JS/TS agent ecosystem (Vercel AI SDK, LangChain.js, MCP) has\n  no strong raw-CDP stealth driver — it's stuck on Playwright + stealth plugins, or\n  shelling out to Python `nodriver`. Veil is that missing piece.\n- **MCP-native.** Ships an MCP server, so any agent gets stealth browsing as tools with\n  zero glue.\n- **Agent-first, not scraper-first.** Accessibility-tree refs and human input are built for\n  an LLM driving the browser, not for a scraping script.\n\nIf you're in Python and just want raw stealth, use `nodriver` or Camoufox — they're great.\nVeil is for **TypeScript agents and MCP hosts.**\n\n## Quick start\n\n**Prerequisites:** Chrome/Chromium on PATH (or `VEIL_CHROME=/path/to/chrome`), and Bun.\n\n```bash\nbun install\nbun run examples/selftest.ts   # launches real Chrome, runs the full chain\n```\n\n**In your code:**\n\n```ts\nimport { Browser } from \"veilbrowser\";\n\nconst browser = await Browser.launch({ headless: false });   // headful = stealthiest\nconst page = await browser.newPage();\nawait page.goto(\"https://example.com\");\n\n// Accessibility-tree snapshot → stable integer refs (no selectors).\nconst snap = await page.snapshot();\nconsole.log(snap.text);\n//  [1] textbox \"Search\"\n//  [2] button \"Sign in\"\n\nawait page.fill(1, \"hello\");          // act by ref — human typing, jittered timing\nawait page.click(2);                  // curved Bézier mouse path, real CDP input\nconst png = await page.screenshot();  // PNG buffer for a vision model\n\nawait browser.close();\n```\n\n## How the stealth works\n\n1. **Launch (`launcher.ts`)** — a real Chrome with the flags a *normal* profile uses,\n   minus the automation switches Playwright adds. `--disable-blink-features=AutomationControlled`\n   flips `navigator.webdriver` to `false` at the engine level. Persistent `userDataDir`\n   so the profile looks *used* (history, cookies), not freshly minted.\n2. **Transport (`cdp.ts`)** — raw WebSocket, flat session mode. **We never call\n   `Runtime.enable`** — that command is a primary CDP detection vector. `Runtime.evaluate`\n   works without it.\n3. **Page patch (`stealth.ts`)** — injected via `addScriptToEvaluateOnNewDocument`\n   *before* any site code, on every frame: normalizes `webdriver`, `window.chrome`,\n   `plugins`, `languages`, `permissions.query`, WebGL vendor — and makes the patched\n   functions' `toString()` look native so the patch itself can't be detected. Kept\n   deliberately small; over-patching is its own fingerprint.\n4. **UA / client hints (`page.ts`)** — strips the `HeadlessChrome` token from the UA\n   *and* the `Sec-CH-UA` brand headers.\n5. **Human input (`human.ts`)** — seedable PRNG drives curved mouse paths and\n   jittered keystroke timing.\n\n## Agent tooling (the other half of the product)\n\nThe selling point isn't only stealth — it's that agents drive it *well*:\n\n- **`snapshot()`** returns the page as a flat numbered index from the accessibility\n  tree (the semantic layer screen readers use). The #1 cause of agent breakage —\n  guessed CSS/XPath selectors — is gone. The agent acts on a stable `ref`.\n- **`screenshot()`** returns a PNG buffer, ready for vision grounding.\n- **`click` / `fill` / `type`** drive real CDP input with human dynamics.\n- **`waitFor(expr)`** replaces flaky fixed sleeps.\n\n## Detection scorecard (measured, Chrome 148)\n\nRun it yourself: `bun run examples/detect.ts` (headless) or `VEIL_HEADFUL=1 bun run\nexamples/detect.ts` (headful — Veil auto-starts its own Xvfb, no wrapper needed).\n\nMeasured on an AMD Radeon (Renoir APU) host, real hardware GL via ANGLE/EGL:\n\n| Detector | Mode | sannysoft | CreepJS \"headless\" | CreepJS \"stealth\" |\n|---|---|---|---|---|\n| **Veil — headful + auto-Xvfb + real GPU** | recommended | **57/57** | **0%** | **0%** |\n| Veil — headless + real GPU | server/fast | **57/57** | 33% | 0% |\n| _(earlier: SwiftShader + heavy stealth)_ | _superseded_ | 57/57 | 67% | 20% |\n\n**Live targets** (`bun run examples/hardtargets.ts`, residential IP):\n\n| Target | Result |\n|---|---|\n| Cloudflare JS challenge (scrapingcourse) | **Bypassed** |\n| Antibot challenge (scrapingcourse) | **Bypassed** |\n| Reddit — incl. its JS challenge | served clean (challenge auto-solved) |\n| Instagram — public profile | served clean |\n\nHonest gaps we do **not** yet claim: interactive Turnstile/reCAPTCHA, enterprise\nDataDome/Kasada, logged-in sessions, high-volume behavioural trust. We test before we claim.\n\n**What moved the needle (each verified by re-running the suite):**\n1. **Real GPU, not SwiftShader.** `--use-gl=angle --use-angle=gl-egl` drives the actual\n   AMD GPU → an authentic, self-consistent WebGL fingerprint. No vendor spoof = no *lie*\n   for CreepJS's pixel-hash to catch.\n2. **Headful on a server.** Veil manages its own Xvfb display, so \"headful\" needs no\n   desktop. Eliminates the headless render quirks + tiny-screen tell (33% → 0%).\n3. **Slim, self-gating stealth.** The biggest surprise: the *stealth patches themselves*\n   were the \"20% stealth\" signal. A correctly-launched Chrome already reports\n   `webdriver === false` (the right human value — forcing `undefined` is *worse*), 5\n   plugins, a real `chrome` object. So each patch now fires only when the value is\n   genuinely anomalous; on healthy Chrome it's a no-op. Smaller surface = nothing to detect.\n\n## Use from an AI agent (MCP)\n\nVeil ships an MCP server (`src/mcp.ts`) — already wired into **persoje**\n(`~/.config/persoje/mcp.json`), exposing 8 tools: `goto`, `snapshot`, `click`, `fill`,\n`type`, `screenshot`, `eval`, `close`. Verified end-to-end through persoje's own MCP\nclient (discover → goto → snapshot). Any MCP host works:\n\n```jsonc\n{ \"servers\": { \"veil\": {\n  \"command\": \"/home/armani/.bun/bin/bun\",\n  \"args\": [\"run\", \"/home/armani/projects/veil/src/mcp.ts\"],\n  \"env\": {}                          // headful + auto-Xvfb + real GPU (0% CreepJS).\n                                     // Set VEIL_HEADLESS=1 for the faster server mode.\n} } }\n```\n\n## Testing\n\n```bash\nbun run examples/selftest.ts   # end-to-end: launch, stealth, snapshot, interact\nbun run examples/detect.ts     # bot-detection scorecard (bot.sannysoft.com, etc.)\ndeno test tests/*.test.ts      # unit tests: PRNG, mouse paths, ref numbering\n```\n\nUnit tests cover:\n- **PRNG (`human.test.ts`)**: xorshift32 determinism, range/int bounds, keystroke cadence, mouse timing\n- **Snapshot refs (`snapshot.test.ts`)**: ref numbering (1-based, sequential, no gaps), AX-tree filtering\n- **CDP framing (`cdp-messages.test.ts`)**: JSON-RPC structure, sessionId routing, command/response correlation\n\n## Status\n\n**Working today** (verified against Chrome 148):\n- Zero-dep CDP runtime (raw WebSocket, flat session mode)\n- Stealth launch + page-script injection (`--disable-blink-features=AutomationControlled`)\n- UA/client-hint scrub (no \"HeadlessChrome\" token)\n- WebGL backend selection (hardware GPU via ANGLE/EGL, or SwiftShader + vendor masking)\n- AX-tree snapshot → stable integer refs for agent-friendly interaction\n- Human-like input: curved Bézier mouse paths, jittered keystroke timing, real CDP input\n- PNG screenshots (vision-model ready)\n- MCP server (`src/mcp.ts`) — stdio JSON-RPC; persoje, Claude, any MCP host drives Veil natively\n\n**Roadmap toward production:**\n- [ ] Adversarial fingerprint suite — continuous scoring (CreepJS, sannysoft, Datadome demo)\n- [ ] `Runtime.enable`-leak hardening via isolated worlds for all eval\n- [ ] Headful-on-server via managed Xvfb; profile + proxy pools\n- [ ] Vision-based element grounding fallback (sparse AX-trees, canvas apps)\n- [ ] Network interception; response capture; session persistence \u0026 profile warm-up\n- [ ] Per-tab concurrency (many tabs, one socket — transport already supports it)\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facunningham-ship-it%2Fveilbrowser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facunningham-ship-it%2Fveilbrowser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facunningham-ship-it%2Fveilbrowser/lists"}