[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-92481":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":42,"readmeContent":43,"aiSummary":44,"trendingCount":15,"starSnapshotCount":15,"syncStatus":14,"lastSyncTime":45,"discoverSource":46},92481,"fortress","tiliondev\u002Ffortress","tiliondev","Stealth Chromium engine that stops scrapers and browser agents from getting blocked, with one line of code change.",null,"Python",305,18,1,2,0,127,53.84,"Other",false,"main",[22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41],"anti-bot","automation","bot-detection","browser-automation","chromium","crawler","data-scraping","fingerprinting","headless-browser","headless-chrome","playwright","puppeteer","python","scraping","selenium","stealth","testing","web-crawling","web-scraping","webscraping","2026-07-22 04:02:06","\u003Cdiv align=\"center\">\n\n\u003Cimg alt=\"Fortress\" src=\"docs\u002Fassets\u002Fbanner-fortress.png\" width=\"100%\">\n\n\n### One browser engine to rule them all\n\nStealth Chromium engine\n\n[![Chromium](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fchromium-151.0.7908.0-4285F4?logo=googlechrome&logoColor=white)](CHROMIUM_VERSION) [![pip](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpip-3776AB?logo=pypi&logoColor=white)](https:\u002F\u002Fpypi.org\u002Fproject\u002Ftilion-fortress\u002F) [![npm](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fnpm-CB3837?logo=npm&logoColor=white)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftilion-fortress) [![Docker pulls](https:\u002F\u002Fimg.shields.io\u002Fdocker\u002Fpulls\u002Ftilion\u002Ffortress?logo=docker&logoColor=white&label=pulls)](https:\u002F\u002Fhub.docker.com\u002Fr\u002Ftilion\u002Ffortress)\u003Cbr\u002F>\n[![CreepJS](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FCreepJS-0%25%20headless-2ea44f)](docs\u002FGAUNTLET_RESULTS.md) [![Runtime.enable leak](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FRuntime.enable-no%20leak-2ea44f)](docs\u002FGAUNTLET_RESULTS.md)\u003Cbr\u002F>\n[![Copy for agent](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FCopy%20for%20agent-24292f?logo=readme&logoColor=white)](https:\u002F\u002Fraw.githubusercontent.com\u002Ftiliondev\u002Ffortress\u002Fmain\u002FAGENTS.md) [![llms.txt](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fllms.txt-24292f?logo=readme&logoColor=white)](https:\u002F\u002Fraw.githubusercontent.com\u002Ftiliondev\u002Ffortress\u002Fmain\u002Fllms.txt)\u003Cbr\u002F>\n[![MCP server](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FMCP-fortress%20·%2029%20tools-6E56CF?logo=modelcontextprotocol&logoColor=white)](mcp\u002F) [![npm tilion-mcp](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002Ftilion-mcp?logo=npm&logoColor=white&label=npx%20tilion-mcp&color=CB3837)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftilion-mcp)\n\n**Fortress is a stealth Chromium engine that stops your scrapers and browser agents from getting blocked, with one line of code change.** Bot detectors flag automation by reading the browser fingerprint; Fortress corrects that fingerprint inside Chromium's C++, so the browser presents as an ordinary Chrome install. Scrapers finish their runs, agents reach the pages they were sent to, and CreepJS, Sannysoft, BrowserScan, and live Cloudflare Turnstile all read it as human. Point your existing Playwright or Puppeteer at Fortress over CDP, and nothing else in your code changes.\n\n\u003Csub>**Blink · V8 · BoringSSL** patched in-tree · **ANGLE \u002F D3D11**-backed WebGL · **JA3\u002FJA4-coherent** TLS · **monthly** upstream rebase · **reproducible, gauntlet-gated** releases\u003C\u002Fsub>\n\n\u003Ctable align=\"center\">\u003Ctr>\n\u003Ctd align=\"center\" width=\"150\">\u003Ch3>34\u003C\u002Fh3>\u003Csub>single-surface\u003Cbr\u002F>C++ patches\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"150\">\u003Ch3>0%\u003C\u002Fh3>\u003Csub>CreepJS\u003Cbr\u002F>headless \u002F stealth\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"160\">\u003Ch3>\u003Ccode>[native&nbsp;code]\u003C\u002Fcode>\u003C\u002Fh3>\u003Csub>across every\u003Cbr\u002F>realm\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"150\">\u003Ch3>BSD-3\u003C\u002Fh3>\u003Csub>open engine,\u003Cbr\u002F>rebuild it yourself\u003C\u002Fsub>\u003C\u002Ftd>\n\u003C\u002Ftr>\u003C\u002Ftable>\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fassets\u002Fdemo.gif\" alt=\"Fortress clearing a live Cloudflare challenge, then passing sannysoft and BrowserScan\" width=\"720\"\u002F>\u003C\u002Fp>\n\n\u003Csub>\u003Ci>Unedited capture of the Fortress binary in a real window: it clears a live \u003Cb>Cloudflare\u003C\u002Fb> challenge, turns \u003Cb>bot.sannysoft.com\u003C\u002Fb> all green, then reads \u003Cb>BrowserScan\u003C\u002Fb> “Normal”. Reproduce with \u003Ccode>tools\u002Fgauntlet.py\u003C\u002Fcode>.\u003C\u002Fi>\u003C\u002Fsub>\n\n\u003C\u002Fdiv>\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Native-code parity\nEvery spoofed getter *is* a C++ getter: `toString` returns `[native code]`, **realm-invariant** across main frame, iframes, and Web Workers.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Drop-in CDP\n**nodriver-style** raw CDP on `:9222`, with no `Runtime.enable` leak. Keep Playwright, Puppeteer, or any CDP client; swap the browser, keep your code.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Clears the gauntlet\n**0% headless** on CreepJS; Sannysoft, BrowserScan, and live Cloudflare Turnstile cleared, all as a stock Chrome install.\n\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Auditable patches\n34 small single-purpose diffs in `patches\u002F`. Read one in a minute; rebuild the engine with one script.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Coherent by construction\nReal V8, Blink, and BoringSSL keep engine, user-agent, and **JA3\u002FJA4 TLS shape** in agreement: a Windows persona on a matching stack.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\" valign=\"top\">\n\n#### Tunable persona\nOne binary, a **coherence-checked** Windows identity; `--uxr-*` switches override any surface: GPU, screen, timezone, hardware, Client-Hints.\n\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n---\n\n## 🆕 What's new — 151.0.7908.0 · Engine Refresh\n\n**Released.** Per-launch coherent personas, hardened.\n\n- **Per-locale keyboard** (QWERTY \u002F QWERTZ \u002F AZERTY) · **per-persona media devices** · a full **delivery-parity** coherence pass.\n- Platform ↔ GPU ↔ timezone ↔ language ↔ voices ↔ keyboard move as **one coherent real device** — on every launch, from a single binary.\n- **Native-code parity** (`toString()` stays `[native code]`), **realm-invariant** (main \u002F worker \u002F iframe) — coherence compiled into the browser, not patched in JavaScript.\n\n```bash\npip install -U tilion-fortress       # or:  docker run --rm -p 9222:9222 tilion\u002Ffortress:latest\n```\n\n**[→ full release notes](https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress\u002Freleases\u002Ftag\u002Fv151.0.7908.0)**\n\n\n## Contents\n\n| | |\n|---|---|\n| **[What it is](#what-it-is)** · **[Quick start](#quick-start)** | what it is, install, first script, AI-agent setup |\n| **[The Fortress MCP](#the-fortress-mcp--stealth-browsing-as-agent-tools)** | 29 stealth-browser tools for AI agents (Beta) |\n| **[Why patch the engine, not the page](#why-patch-the-engine-not-the-page)** | the self-revealing-JS thesis + the three detection layers |\n| **[How Fortress compares](#how-fortress-compares)** | vs puppeteer-stealth · Camoufox · CloakBrowser · closed vendors |\n| **[Proof: live-detector results](#proof-live-detector-results)** | CreepJS \u002F Sannysoft \u002F BrowserScan \u002F Cloudflare, with screenshots |\n| **[Configure the persona](#configure-the-persona)** | the `--uxr-*` fingerprint surface |\n| **[Works with your stack](#works-with-your-stack)** | browser-use · Crawl4AI · Stagehand · LangChain |\n| **[Build & verify](#build--verify)** | reproduce from source, verify provenance |\n| **[Reference](#reference)** | troubleshooting · FAQ · roadmap · repo layout |\n\n---\n\n## What it is\n\nFortress is a Chromium fork that spoofs the browser fingerprint from inside the engine. The surfaces bot detectors read (canvas, WebGL, audio, fonts, navigator, and about thirty more) are corrected in Chromium's **C++**, with no JavaScript patch layer sitting on top for a page to catch.\n\nIt ships as an ordinary browser binary that exposes a CDP endpoint. Point Playwright, Puppeteer, or any CDP client at it and your existing automation runs unchanged.\n\nA JavaScript stealth patch leaves an extra layer the page can find: `.toString()` shows the override's source, and re-grabbing the same primitive from an iframe or worker reaches past it. Fortress corrects the surface in the engine instead, so `navigator.vendor` resolves to the real C++ getter, reports `[native code]`, and reads the same from every realm. A page inspecting itself sees stock Chromium. That is why your automation gets through where it used to get flagged, and whatever blocking is left traces to your proxies and behavior rather than the browser. [Why patch the engine, not the page](#why-patch-the-engine-not-the-page) covers the detection mechanics in full.\n\n```python\nfrom tilion_fortress import Fortress\nfrom playwright.sync_api import sync_playwright\n\nwith Fortress() as f:                                   # launches the stealth engine on a CDP endpoint\n    with sync_playwright() as p:\n        browser = p.chromium.connect_over_cdp(f.cdp_url)\n        page = browser.new_page()\n        page.goto(\"https:\u002F\u002Fbot.sannysoft.com\")\n        page.screenshot(path=\"all-green.png\")\n```\n```js\nimport { Fortress } from \"tilion-fortress\";\nimport { chromium } from \"playwright\";\n\nconst f = await Fortress.launch();                      \u002F\u002F stealth engine on a CDP endpoint\nconst browser = await chromium.connectOverCDP(f.cdpUrl);\nconst page = await browser.newPage();\nawait page.goto(\"https:\u002F\u002Fbrowserscan.net\");\nawait browser.close();\nawait f.close();\n```\n\n\u003Cdiv align=\"center\">\n\n### The 12-second tour\n\n\u003Cimg src=\"docs\u002Fassets\u002Ffortress-reel.gif\" width=\"760\" alt=\"Fortress in 12 seconds: passes CreepJS, Sannysoft, BrowserScan and rebrowser, scrapes real sites, and clears Akamai on aa.com, lowes.com, macys.com and kohls.com.\"\u002F>\n\n\u003Csub>One loop, all real captures: passes \u003Cb>CreepJS \u002F Sannysoft \u002F BrowserScan \u002F rebrowser\u003C\u002Fb> → scrapes structured data over CDP → clears \u003Cb>Akamai\u003C\u002Fb> on aa.com · lowes · macys · kohls (same residential IP).\u003C\u002Fsub>\n\n\u003C\u002Fdiv>\n\n\u003Cdiv align=\"center\">\n\n### Real scraping, fully headless\n\n\u003Csub>Unedited captures of the Fortress engine driven over CDP. No stealth plugins, no JS patches: the fingerprint is corrected in the binary. Reproduce any of these with \u003Ca href=\"examples\u002Fscrape_demos.py\">\u003Ccode>examples\u002Fscrape_demos.py\u003C\u002Fcode>\u003C\u002Fa>.\u003C\u002Fsub>\n\n\u003Cimg src=\"docs\u002Fassets\u002Ffortress-scrape-structured.gif\" width=\"720\" alt=\"Fortress extracting books.toscrape.com into typed JSON records live over CDP\"\u002F>\n\n\u003Csub>\u003Cb>Structured extraction\u003C\u002Fb>: records build into typed JSON as each item is read.\u003C\u002Fsub>\n\n\u003Ctable>\u003Ctr>\n\u003Ctd align=\"center\" width=\"50%\">\u003Cimg src=\"docs\u002Fassets\u002Ffortress-scrape-paginated.gif\" width=\"358\" alt=\"Fortress auto-paginating across pages of quotes.toscrape.com\"\u002F>\u003Cbr\u002F>\u003Csub>\u003Cb>Auto-pagination\u003C\u002Fb>: 30 quotes across 3 pages.\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"50%\">\u003Cimg src=\"docs\u002Fassets\u002Ffortress-scrape-detail.gif\" width=\"358\" alt=\"Fortress deep-crawling a product detail page\"\u002F>\u003Cbr\u002F>\u003Csub>\u003Cb>Deep detail crawl\u003C\u002Fb>: UPC · price · tax · stock · reviews.\u003C\u002Fsub>\u003C\u002Ftd>\n\u003C\u002Ftr>\u003C\u002Ftable>\n\n\u003C\u002Fdiv>\n\n\u003Cdiv align=\"center\">\n\n### Clears real Akamai — before \u002F after\n\n\u003Cimg src=\"docs\u002Fassets\u002Ffortress-akamai.gif\" width=\"760\" alt=\"Before: a stock browser is blocked by Akamai on aa.com with Access Denied. After: Fortress loads the real page and Akamai's sensor accepts it.\"\u002F>\n\n\u003Csub>Same residential IP, same site (\u003Cb>aa.com\u003C\u002Fb> · Akamai Bot Manager). A stock\u002Fheadless browser gets \u003Cb>Access Denied\u003C\u002Fb> (Reference&nbsp;#); Fortress loads the real page and Akamai issues its \u003Ccode>_abck\u003C\u002Fcode> sensor cookie — the Bot Manager accepts it as a real browser. The variable is the \u003Cb>fingerprint\u003C\u002Fb>, not the IP.\u003C\u002Fsub>\n\n\u003Ctable>\u003Ctr>\n\u003Ctd align=\"center\" width=\"33%\">\u003Cimg src=\"docs\u002Fassets\u002Ffortress-akamai-lowes.gif\" width=\"250\" alt=\"Fortress clearing Akamai on lowes.com\"\u002F>\u003Cbr\u002F>\u003Csub>\u003Cb>lowes.com\u003C\u002Fb> · blocked → cleared\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"33%\">\u003Cimg src=\"docs\u002Fassets\u002Ffortress-akamai-macys.gif\" width=\"250\" alt=\"Fortress clearing Akamai on macys.com\"\u002F>\u003Cbr\u002F>\u003Csub>\u003Cb>macys.com\u003C\u002Fb> · blocked → cleared\u003C\u002Fsub>\u003C\u002Ftd>\n\u003Ctd align=\"center\" width=\"33%\">\u003Cimg src=\"docs\u002Fassets\u002Ffortress-akamai-kohls.gif\" width=\"250\" alt=\"Fortress clearing Akamai on kohls.com\"\u002F>\u003Cbr\u002F>\u003Csub>\u003Cb>kohls.com\u003C\u002Fb> · blocked → cleared\u003C\u002Fsub>\u003C\u002Ftd>\n\u003C\u002Ftr>\u003C\u002Ftable>\n\n\u003Csub>Not a one-site fluke — same before\u002Fafter on major Akamai-protected retailers, every run from the same residential IP.\u003C\u002Fsub>\n\n\u003C\u002Fdiv>\n\n---\n\n## Quick start\n\n\n```bash\n# Python \u002F Node: prebuilt native binary auto-fetched (Linux x64 & Windows x64), SHA-256 verified\npip install tilion-fortress\nnpm  install tilion-fortress\n\n# Any OS via Docker: raw CDP on :9222  (~302 MB pull \u002F 851 MB on disk, stripped single-layer)\ndocker run --rm -p 9222:9222 tilion\u002Ffortress:latest\n\n# Portable bundle (extract-and-run, like a Chromium snapshot)\ntar xzf tilion-fortress-linux-x64.tar.gz            # Linux\n.\u002Ftilion-fortress\u002Ftilion --headless=new --remote-debugging-port=9222 --user-data-dir=\u002Ftmp\u002Fp\n\n# Debian \u002F Ubuntu\nsudo apt install .\u002Ftilion-fortress_151.0.7908.0_amd64.deb && tilion https:\u002F\u002Fexample.com\n```\n\n> [!TIP]\n> The SDK ships the compiled build **plus `patches\u002F`**, so you can rebuild the engine yourself and verify every surface correction against the source. Downloads are SHA-256-verified against the release `SHA256SUMS` automatically.\n\n### Versions\n\nFortress ships on **two Chromium bases** — pick your trade-off between blend-in and currency:\n\n| Channel | Chromium | When to use |\n|---|---|---|\n| **`stable`** *(default)* | **149** | Recommended — matches the Chrome version the mass of real users run, so it blends in best |\n| **`latest`** | **151** | Newest engine (reports a version slightly ahead of stable) |\n\n```python\nFortress().start()                     # Python: stable (149) by default\nFortress(channel=\"latest\").start()     # opt into 151\n```\n```js\nawait Fortress.launch();               \u002F\u002F Node: stable (149) by default\nawait Fortress.launch({ channel: \"latest\" });\n```\n```bash\ndocker run --rm -p 9222:9222 tilion\u002Ffortress:149   # or :151\n# or set FORTRESS_CHANNEL=latest for either SDK\n```\n\nNative binaries: **Linux x64** (both versions) + **Windows x64** (151); Windows-149 and macOS run via the Docker image.\n\n### Drop it into your AI agent\n\nFortress is the browser your agent drives: raw CDP on `:9222`, no stealth plugins to wire up. There are two ways in.\n\n**Option 1: open it pre-loaded in a chat assistant.** One click; it reads our [AGENTS.md](AGENTS.md) and walks you through the whole setup:\n\n[![Ask ChatGPT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FAsk-ChatGPT-10A37F?logo=openai&logoColor=white)](https:\u002F\u002Fchatgpt.com\u002F?q=Help%20me%20set%20up%20Fortress%2C%20an%20open-source%20stealth%20Chromium%20engine%2C%20for%20my%20browser%20automation.%20First%20read%20the%20setup%20guide%20at%20https%3A%2F%2Fgithub.com%2Ftiliondev%2Ffortress%2Fblob%2Fmain%2FAGENTS.md%20then%20walk%20me%20through%3A%201%29%20launching%20Fortress%20%28Docker%3A%20docker%20run%20-d%20--rm%20-p%209222%3A9222%20tilion%2Ffortress%3Alatest%2C%20or%20pip%2Fnpm%20install%20tilion-fortress%29%2C%202%29%20connecting%20my%20Playwright%20or%20Puppeteer%20code%20over%20CDP%20to%20http%3A%2F%2Flocalhost%3A9222%2C%203%29%20keeping%20my%20existing%20automation%20logic.%20Do%20NOT%20add%20puppeteer-stealth%20or%20JS%20fingerprint%20patches%20%E2%80%94%20Fortress%20spoofs%20the%20fingerprint%20in%20the%20engine%27s%20C%2B%2B.) [![Ask Claude](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FAsk-Claude-D97757?logo=claude&logoColor=white)](https:\u002F\u002Fclaude.ai\u002Fnew?q=Help%20me%20set%20up%20Fortress%2C%20an%20open-source%20stealth%20Chromium%20engine%2C%20for%20my%20browser%20automation.%20First%20read%20the%20setup%20guide%20at%20https%3A%2F%2Fgithub.com%2Ftiliondev%2Ffortress%2Fblob%2Fmain%2FAGENTS.md%20then%20walk%20me%20through%3A%201%29%20launching%20Fortress%20%28Docker%3A%20docker%20run%20-d%20--rm%20-p%209222%3A9222%20tilion%2Ffortress%3Alatest%2C%20or%20pip%2Fnpm%20install%20tilion-fortress%29%2C%202%29%20connecting%20my%20Playwright%20or%20Puppeteer%20code%20over%20CDP%20to%20http%3A%2F%2Flocalhost%3A9222%2C%203%29%20keeping%20my%20existing%20automation%20logic.%20Do%20NOT%20add%20puppeteer-stealth%20or%20JS%20fingerprint%20patches%20%E2%80%94%20Fortress%20spoofs%20the%20fingerprint%20in%20the%20engine%27s%20C%2B%2B.) [![Ask Gemini](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FAsk-Gemini-1C69FF?logo=googlegemini&logoColor=white)](https:\u002F\u002Fgemini.google.com\u002Fapp?q=Help%20me%20set%20up%20Fortress%2C%20an%20open-source%20stealth%20Chromium%20engine%2C%20for%20my%20browser%20automation.%20First%20read%20the%20setup%20guide%20at%20https%3A%2F%2Fgithub.com%2Ftiliondev%2Ffortress%2Fblob%2Fmain%2FAGENTS.md%20then%20walk%20me%20through%3A%201%29%20launching%20Fortress%20%28Docker%3A%20docker%20run%20-d%20--rm%20-p%209222%3A9222%20tilion%2Ffortress%3Alatest%2C%20or%20pip%2Fnpm%20install%20tilion-fortress%29%2C%202%29%20connecting%20my%20Playwright%20or%20Puppeteer%20code%20over%20CDP%20to%20http%3A%2F%2Flocalhost%3A9222%2C%203%29%20keeping%20my%20existing%20automation%20logic.%20Do%20NOT%20add%20puppeteer-stealth%20or%20JS%20fingerprint%20patches%20%E2%80%94%20Fortress%20spoofs%20the%20fingerprint%20in%20the%20engine%27s%20C%2B%2B.) [![Copy for agent](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FCopy%20for%20agent-full%20context-24292f?logo=readme&logoColor=white)](https:\u002F\u002Fraw.githubusercontent.com\u002Ftiliondev\u002Ffortress\u002Fmain\u002FAGENTS.md)\n\n**Option 2: Copy for agent (everything, to your clipboard).** Hit the copy icon at the **top-right of the box** below. It puts the *entire* setup context on your clipboard: what it is, install, connect, persona, and rules, all of [AGENTS.md](AGENTS.md) condensed. Paste it into Cursor, Claude Code, Copilot, ChatGPT, or any agent and it takes it from there:\n\n```text\nYou're setting up Fortress, an open-source STEALTH Chromium engine, for browser automation.\nIt corrects the browser fingerprint (canvas, WebGL, audio, fonts, navigator, +30 more) in Chromium's\nC++ and exposes raw CDP on http:\u002F\u002Flocalhost:9222, a drop-in for Playwright\u002FPuppeteer. Do NOT add\npuppeteer-stealth or any JS fingerprint patching (it self-reveals and undoes Fortress).\n\nLAUNCH (pick one; all expose CDP on http:\u002F\u002Flocalhost:9222):\n  Docker:  docker run -d --rm -p 9222:9222 tilion\u002Ffortress:latest\n  Python:  pip install tilion-fortress    then  from tilion_fortress import Fortress; f=Fortress(); f.start()\n  Node:    npm install tilion-fortress    then  import {Fortress} from \"tilion-fortress\"; const f=await Fortress.launch()\n\nCONNECT (keep my existing automation code):\n  Playwright(py):  browser = p.chromium.connect_over_cdp(\"http:\u002F\u002Flocalhost:9222\")\n  Playwright(js):  const browser = await chromium.connectOverCDP(\"http:\u002F\u002Flocalhost:9222\")\n  Puppeteer(js):   const browser = await puppeteer.connect({ browserURL: \"http:\u002F\u002Flocalhost:9222\" })\n  browser-use \u002F Crawl4AI \u002F Stagehand \u002F LangChain:  point their CDP endpoint at http:\u002F\u002Flocalhost:9222\n\nPERSONA (optional; default is a coherent Windows identity). Override any surface with --uxr-* flags:\n  --uxr-timezone=America\u002FNew_York --uxr-hw-concurrency=16 --uxr-languages=en-US,en\n\nRULES:\n  1) Drive over raw CDP (:9222); don't spawn chromedriver.\n  2) Never pass --user-agent (use --uxr-ua-*); it desyncs UA vs UA-Client-Hints.\n  3) No puppeteer-stealth \u002F undetected-chromedriver \u002F JS fingerprint patches.\n  4) Blocked ~90% = my IP (datacenter), not the fingerprint. Use a residential\u002Fmobile proxy, then retry.\n\nNow walk me through launching Fortress and wiring my automation to it.\nFull guide: https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress\u002Fblob\u002Fmain\u002FAGENTS.md\n```\n\n---\n\n## The Fortress MCP — stealth browsing as agent tools &nbsp;\u003Csub>Beta\u003C\u002Fsub>\n\nRaw CDP is for code you write. The **Fortress MCP** is for agents that call **tools**: a [Model Context Protocol](https:\u002F\u002Fmodelcontextprotocol.io) server that hands Claude, Cursor, or any MCP client a stealth browser, so the moment a fetch is blocked it just calls a tool and gets the page. **29 tools, local and free** — `fetch_protected_page`, `extract_page`, `crawl_site`, `recon_site_apis`, `search_web`, `run_browser_task`, `save_profile`, `get_stealth_cdp_endpoint`, and more.\n\n\u003Cp align=\"center\">\u003Cimg src=\"mcp\u002Fdemo.gif\" alt=\"Same site, same prompt: a vanilla browser is blocked by PerimeterX while an agent with the Fortress MCP returns clean JSON\" width=\"760\"\u002F>\u003C\u002Fp>\n\n\u003Csub>\u003Ci>Real, dated run against \u003Cb>stockx.com\u003C\u002Fb> (PerimeterX). A stock browser gets \u003Cb>HTTP 403 — “Access denied”\u003C\u002Fb>; an agent with the Fortress MCP returns clean JSON — same site, same prompt. Reproduce it from the framework repo.\u003C\u002Fi>\u003C\u002Fsub>\n\n### Set it up in 30 seconds\n\nTwo runners — pick one. `npx` needs Python on PATH; `pip` installs it directly:\n\n```bash\npip install \"tilion[mcp]\"      # command:  tilion-mcp\n#   —or, zero-install—\nnpx -y tilion-mcp              # auto-runs the server via uv (no global install)\n```\n\n**Claude Desktop** — Settings → Developer → *Edit Config* (`claude_desktop_config.json`):\n\n```json\n{ \"mcpServers\": { \"fortress\": { \"command\": \"tilion-mcp\" } } }\n```\n\u003Csub>Prefer npx? Use \u003Ccode>\"command\": \"npx\", \"args\": [\"-y\", \"tilion-mcp\"]\u003C\u002Fcode>. Restart Claude, and the \u003Cb>fortress\u003C\u002Fb> tools appear.\u003C\u002Fsub>\n\n**Claude Code** (CLI) — one line:\n\n```bash\nclaude mcp add fortress -- tilion-mcp          # or:  claude mcp add fortress -- npx -y tilion-mcp\n```\n\n**Cursor** (`~\u002F.cursor\u002Fmcp.json`) · **Cline \u002F Windsurf** (VS Code → MCP servers) — same block:\n\n```json\n{ \"mcpServers\": { \"fortress\": { \"command\": \"tilion-mcp\" } } }\n```\n\nThen just ask your agent — *“get the price off this StockX page”* — and it calls `fetch_protected_page` on its own.\n\n### What the agent gets\n\n| | tools |\n|---|---|\n| **Get blocked pages** | `fetch_protected_page` · `read_page` · `get_page_html` · `search_web` |\n| **Structured data** | `extract_page` (schema-aware) · `extract_document` (PDF\u002FDOCX\u002FXLSX) |\n| **Whole sites** | `crawl_site` (auto-SPA) · `recon_site_apis` (find the private JSON API) |\n| **Drive a page** | `page_elements` · `click_button` · `fill_field` · `press_key` · `wait_for` · `evaluate_js` |\n| **Multi-step flows** | `run_browser_task` (login, paginate, infinite-scroll, checkout, …) |\n| **Capture \u002F auth** | `screenshot_page` · `save_page` · `download_file` · `save_profile` \u002F `load_profile` |\n| **Bring your own** | `get_stealth_cdp_endpoint` → a CDP url for Playwright \u002F Puppeteer \u002F browser-use |\n\nTools are annotated (reads auto-approve, writes gate), **pre-warmed** on startup (~100 ms first call), concurrency-safe, and timeout- and SSRF-guarded. A hosted endpoint with **residential egress is coming soon**.\n\n→ Full 29-tool table, benchmarks, and the agent skill: **[`mcp\u002F`](mcp\u002FREADME.md)**\n\n---\n\n## Why patch the engine, not the page\n\nThe usual approach patches `navigator.webdriver`, spoofs the WebGL vendor, and overrides `navigator.plugins` from script. CreepJS and similar detectors still flag it, and the reason is **structural**, not one more property left uncovered. A JavaScript spoof is a function standing where a native one belongs. Detectors set the returned value aside and interrogate whether the thing returning it is native:\n\n| The tell | Why it catches a JS spoof |\n|---|---|\n| `toString` self-reveal | A native method stringifies to `function get vendor() { [native code] }`; an override stringifies to its own source, so one `.toString()` catches it. |\n| Descriptor and `hasOwnProperty` | `getOwnPropertyDescriptor` exposes redefined props, and `hasOwnProperty('toString')` returns `true` on a tampered function where a native one returns `false`. |\n| `failsTypeError` | Native getters throw a specific `TypeError` on the wrong `this`; a naive shim stays quiet, and the silence is the signal. |\n\nRealm re-acquisition is the one that defeats every main-world patch. A detector grabs a pristine primitive from another realm and turns it on your function:\n\n```js\nconst iframe = document.createElement('iframe'); document.body.appendChild(iframe);\nconst realToString = iframe.contentWindow.Function.prototype.toString;\nrealToString.call(navigator.__lookupGetter__('vendor')); \u002F\u002F returns your source code. Caught.\n```\n\nYour main-world patch lives in a different realm from that iframe. The same trap fires from a Web Worker, a thread your main-thread shim runs *beside* rather than *inside*.\n\nFortress has no such layer. The getter for `navigator.vendor` **is** the C++ getter: it reports `[native code]` because it is native code, identical across every realm. Camoufox puts it well: *\"there is no JavaScript hijacking to be detected.\"* Fortress applies the same idea to **V8 and Blink** in place of Gecko.\n\n### The three layers of bot detection, and where Fortress fits\n\nModern anti-bots (Cloudflare, DataDome, Kasada, HUMAN, Akamai) read three structurally different surfaces, in three separate places. One tool rarely fixes all three:\n\n| Layer | The tells | Where the fix lives | Fortress |\n|---|---|---|---|\n| **A: driver \u002F binary artifacts** | `cdc_` ChromeDriver vars, WebDriver protocol surface | Drive raw CDP, skip chromedriver | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> built to be driven this way |\n| **B: CDP side-effects** | `Runtime.enable` leaks via sourceURL + init-script footprints, however clean the binary is | The control \u002F CDP-client layer: hold back `Runtime.enable`, use `Runtime.addBinding` + isolated worlds | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> no leak (verified) |\n| **C: fingerprint surface** | canvas, WebGL, audio, fonts, navigator, across main frame, iframes, workers | The engine (C++), because JS overrides self-reveal | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> **this is Fortress** |\n\nFortress is the **Layer-C engine**, built to be driven so A and B hold too. The binary alone leaves the CDP channel open. That part is on the control layer, and pretending otherwise is how you get caught.\n\n---\n\n## How Fortress compares\n\n| | Stock Playwright | puppeteer-extra-stealth | undetected-chromedriver | Camoufox | CloakBrowser | **Fortress** |\n|---|:---:|:---:|:---:|:---:|:---:|:---:|\n| Spoof layer | none | JS injection | CDP\u002Fconfig patch | **C++ engine** | **C++ engine** | **C++ engine** |\n| `toString` yields `[native code]` | n\u002Fa | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | n\u002Fa | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| Survives realm re-acquisition (iframe\u002Fworker) | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| No `Runtime.enable` leak | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fwarn.svg\" width=\"15\" alt=\"partial\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| Engine = Chrome \u002F **V8** (majority traffic) | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> Firefox | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| Coherent Chromium TLS shape | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> Firefox | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| **Fully open-source engine** | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fwarn.svg\" width=\"15\" alt=\"partial\"> latest major paywalled | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| Published, auditable patch series | n\u002Fa | n\u002Fa | n\u002Fa | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fwarn.svg\" width=\"15\" alt=\"partial\"> binary only | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| Reproducible from-source build | n\u002Fa | n\u002Fa | n\u002Fa | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n| **States its own limits** | n\u002Fa | n\u002Fa | n\u002Fa | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fx.svg\" width=\"15\" alt=\"no\"> | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> |\n\nFortress builds on real prior art: [`fingerprint-chromium`](https:\u002F\u002Fgithub.com\u002Fadryfish\u002Ffingerprint-chromium), [`ChromiumFish`](https:\u002F\u002Fgithub.com\u002Farman-bd\u002Fchromiumfish), and CloakBrowser came first, and commercial vendors (Multilogin, Kameleo, GoLogin, AdsPower, Browserbase, Surfsky) recompile Chromium behind closed source. Most of that work stays closed: the paywalled forks hand you a binary and ask you to trust it, and the vendors keep their patches in-house.\n\nFortress goes the other way, because **a stealth engine only stays useful when the people relying on it can see how it works.** Every surface correction lives in `patches\u002F` as a small, single-purpose diff you can read in a minute, and the whole engine rebuilds from source with one script. When a detector finds a new tell, you trace the fix, patch it, and send it back. That feedback loop is the point, and it only works while the engine stays open enough to read, extend, and rebuild.\n\n---\n\n## Proof: live-detector results\n\n*Reproduce any row with `tools\u002Fgauntlet.py --bundle .\u002Ftilion-fortress`. Verified against live detectors; re-run dated in [docs\u002FGAUNTLET_RESULTS.md](docs\u002FGAUNTLET_RESULTS.md).*\n\n| Suite | Stock Chromium | **Fortress** |\n|---|:---:|:---:|\n| **CreepJS** | flagged headless | **0% headless · 0% stealth**, worker signals coherent |\n| **bot.sannysoft.com** | red rows | **0 failed** · WebDriver Advanced passed · WebGL = NVIDIA RTX 3060 \u002F ANGLE D3D11 |\n| **browserscan.net** | bot detected | **“No bots detected, could be a human”** |\n| **rebrowser bot-detector** | `Runtime.enable` LEAK | **no leak** · `webdriver=false` · clean init-scripts (raw CDP) |\n| **Cloudflare Turnstile** | blocked | **bypassed**: a human click cleared a live challenge (headed, datacenter IP) |\n\n\u003Cdetails open>\u003Csummary>\u003Cb>Proof: real, unedited screenshots\u003C\u002Fb>\u003C\u002Fsummary>\n\n\u003Cbr\u002F>\n\n\u003Cimg src=\"docs\u002Fassets\u002Fsannysoft_top.png\" width=\"680\"\u002F>\n\n| BrowserScan | CreepJS | Cloudflare |\n|:---:|:---:|:---:|\n| \u003Cimg src=\"docs\u002Fassets\u002Fbrowserscan_top.png\" width=\"240\"\u002F> | \u003Cimg src=\"docs\u002Fassets\u002Fcreepjs.png\" width=\"240\"\u002F> | \u003Cimg src=\"docs\u002Fassets\u002Fcloudflare_bypass.gif\" width=\"240\"\u002F> |\n\n\u003C\u002Fdetails>\n\n---\n\n## Configure the persona\n\nThe binary carries **zero brand strings**; the launcher applies a coherent default Windows persona. Override any surface with `--uxr-*` switches, or set `TILION_NO_DEFAULTS=1` for a bare launch.\n\n```\n--uxr-platform \u002F --uxr-ua-platform \u002F --uxr-ua-os \u002F --uxr-ua-arch \u002F --uxr-ua-bitness\n--uxr-ua-platform-version \u002F --uxr-ua-brand \u002F --uxr-hw-concurrency \u002F --uxr-device-memory\n--uxr-webgl-vendor \u002F --uxr-webgl-renderer \u002F --uxr-webgl-fullparams\n--uxr-canvas-seed \u002F --uxr-audio-seed \u002F --uxr-timezone \u002F --uxr-languages\n--uxr-screen-width \u002F --uxr-screen-height \u002F --uxr-webrtc-policy=disable_non_proxied_udp\n```\n\n| Env var | Purpose |\n|---|---|\n| `TILION_NO_DEFAULTS=1` | Skip the default persona (bare launch) |\n| `TILION_TZ` \u002F `TILION_LANG` | Quick timezone \u002F language override |\n\n> [!NOTE]\n> **The persona rides on the command line today.** The `--uxr-*` switches are world-readable via `\u002Fproc\u002F\u003Cpid>\u002Fcmdline`, so it's one persona per launch and visible to other processes on the host. The `MaskConfig` runtime lands next: it delivers the persona over IPC and lifts the one-per-process limit (see [what's next](#whats-next)).\n\n---\n\n## Works with your stack\n\nFortress exposes raw CDP on `:9222`, so it drops in under anything that speaks Playwright, Puppeteer, or CDP.\n\n| Framework | Connect via |\n|---|---|\n| [**browser-use**](https:\u002F\u002Fgithub.com\u002Fbrowser-use\u002Fbrowser-use) (~70k stars) | `cdp_url=\"http:\u002F\u002Flocalhost:9222\"` |\n| [**Crawl4AI**](https:\u002F\u002Fgithub.com\u002Funclecode\u002Fcrawl4ai) (~58k stars) | CDP endpoint |\n| [**Stagehand**](https:\u002F\u002Fgithub.com\u002Fbrowserbase\u002Fstagehand) (~21k stars) | `connectOverCDP` |\n| [**LangChain**](https:\u002F\u002Fgithub.com\u002Flangchain-ai\u002Flangchain) Playwright toolkit | Playwright CDP |\n| **Playwright \u002F Puppeteer** (Python & JS) | `connect_over_cdp` \u002F `connect` |\n\n```python\nfrom playwright.sync_api import sync_playwright\nwith sync_playwright() as p:\n    browser = p.chromium.connect_over_cdp(\"http:\u002F\u002Flocalhost:9222\")   # Fortress under the hood\n```\n\n---\n\n## Build & verify\n\n### Reproduce from source\n\n```bash\nexport CHROMIUM_VERSION=$(cat CHROMIUM_VERSION)\nbuild\u002Fbuild.sh                         # depot_tools, sync the tag, apply patches, gn gen, ninja\nbuild\u002Frebase-monthly.sh 152.0.XXXX.0   # bump + 3-way apply + rebuild + gauntlet-gate\n```\nOutput: `out\u002FFortress\u002Fchrome`. The fork is 34 small single-surface patches (`patches\u002F`), so most re-apply cleanly across upstream releases; the gauntlet then gates the release on any regression.\n\n| Platform | Status |\n|---|---|\n| Linux x64 (native) · **Windows x64 (native)** · any OS via Docker | \u003Cimg src=\"docs\u002Fassets\u002Ficons\u002Fcheck.svg\" width=\"15\" alt=\"yes\"> **shipping** |\n| Code-signed installers · macOS `.app` · `linux\u002Farm64` | in progress |\n\n### Verify it's really ours\n\nFortress ships from four official channels. Treat anything else as untrusted:\n\n| | Official source |\n|---|---|\n| **Source** | [github.com\u002Ftiliondev\u002Ffortress](https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress) |\n| **Docker** | [`tilion\u002Ffortress`](https:\u002F\u002Fhub.docker.com\u002Fr\u002Ftilion\u002Ffortress) |\n| **Python** | [`tilion-fortress`](https:\u002F\u002Fpypi.org\u002Fproject\u002Ftilion-fortress\u002F) |\n| **Node** | [`tilion-fortress`](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftilion-fortress) |\n\n**Verify a download.** Every release ships `SHA256SUMS`, and the `pip`\u002F`npm` SDKs run this for you on install:\n\n```bash\nBASE=https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress\u002Freleases\u002Fdownload\u002Fv151.0.7908.0\ncurl -LO $BASE\u002Ftilion-fortress-linux-x64.tar.gz\ncurl -Ls $BASE\u002FSHA256SUMS | sha256sum -c --ignore-missing     # -> OK\n```\n\n**Verify the Docker image** by digest (not just the tag):\n\n```bash\ndocker pull tilion\u002Ffortress:151.0.7908.0\ndocker inspect --format '{{index .RepoDigests 0}}' tilion\u002Ffortress:151.0.7908.0\n# compare the printed sha256:... against the digest in the GitHub Release notes\n```\n\n**Or trust nothing and rebuild it.** The whole fork is 34 readable patches in `patches\u002F`; `build\u002Fbuild.sh` reproduces the binary from Chromium source, so you can diff what you built against what we ship.\n\n---\n\n## Reference\n\n\u003Cdetails>\u003Csummary>\u003Cb>Troubleshooting\u003C\u002Fb>\u003C\u002Fsummary>\n\n\u003Cbr\u002F>\n\n**Still blocked on Cloudflare, DataDome, or Kasada.** Most of the time this is your **IP**, not your fingerprint: a datacenter range gets flagged before any page script runs. Route egress through residential or mobile proxies and retry; if it clears, the fingerprint was fine.\n\n**The fingerprint looks off on a Linux host.** The default persona is Windows, but the TLS shape and some OS-facing signals follow the machine underneath. Match the persona to your egress OS, or set the relevant `--uxr-*` flags so the OS story agrees with where the traffic leaves from.\n\n**macOS pulls a Docker image.** Native Linux + Windows binaries ship today; macOS still runs Fortress through the official Docker image (`tilion\u002Ffortress`). Install Docker Desktop, or run on Linux\u002FWindows x64 for the native binary.\n\n**The persona shows up in `\u002Fproc\u002F\u003Cpid>\u002Fcmdline`.** The `--uxr-*` flags are readable by other processes on the host, one persona per launch. Until the runtime `MaskConfig` lands, keep one persona per process and avoid sharing the host with untrusted code.\n\n**A detector flags something the gauntlet passes.** Detection moves. Confirm you're on the current Chromium rebase, then open an issue with the test page. That page becomes the next patch.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>\u003Cb>FAQ\u003C\u002Fb>\u003C\u002Fsummary>\n\n\u003Cbr\u002F>\n\n**Is this legal?** Fortress is a browser engineering project for legitimate automation, testing, and scraping of publicly available data. Respect each site's ToS and the law in your jurisdiction.\n\n**Is it really free?** Yes. BSD-3, fully open, and self-hostable. The patch series is published, so you can build the current engine from source yourself.\n\n**Why not just use puppeteer-stealth or undetected-chromedriver?** They patch the JS\u002FCDP layer *after* the page can inspect the browser, so they self-reveal via `toString` and realm re-acquisition. Fortress moves the spoof into C++, where the page finds native code. (See \"Why patch the engine, not the page.\")\n\n**How is this different from Camoufox?** Same C++-interception idea. Camoufox forks Firefox (~3% of traffic, a standing anomaly) while Fortress forks Chromium and V8 (the majority engine), so a Chrome user-agent is coherent by construction.\n\n**Will it pass everything forever?** No. Detection keeps moving, so we ship a dated, reproducible gauntlet and a monthly Chromium rebase; you can always see exactly what passes today.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>\u003Cb>Roadmap\u003C\u002Fb>\u003C\u002Fsummary>\n\n\u003Cbr\u002F>\n\n- [ ] Runtime JSON config into a C++ `MaskConfig` (one binary, many coherent fingerprints, nothing on the command line)\n- [ ] First-party MCP server plus Puppeteer \u002F raw-CDP SDKs (drop-in for AI agents)\n- [ ] Code-signed Windows `.exe` and macOS `.app`\n- [ ] `linux\u002Farm64` Docker image\n- [ ] Migrate `patches\u002F` to Brave-style `chromium_src\u002F` overrides\n- [ ] Published reCAPTCHA v3 \u002F DataDome \u002F Kasada benchmark rows (dated, reproducible)\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>\u003Cb>Repo layout\u003C\u002Fb>\u003C\u002Fsummary>\n\n```\npatches\u002F     34 per-surface C++ patches (+ series), the source of truth for the fork\nbuild\u002F       args.gn, build.sh, apply-patches.sh, rebase-monthly.sh, windows\u002F, macos\u002F\npackaging\u002F   tilion launcher, fonts.conf, Dockerfile, .deb + bundle builders\nfonts\u002F       33 metric-compatible Windows-named fonts (incl. color emoji)\nsdk\u002F         python + node (tilion-fortress) prebuilt-binary SDKs\ntools\u002F       gauntlet.py, the CreepJS \u002F Sannysoft \u002F BrowserScan CI gate\ndocs\u002F        GAUNTLET_RESULTS, BUILD_NATIVE, BENCHMARK\n```\n\n\u003C\u002Fdetails>\n\n### Contributing\n\nFound a detection vector or a leak we missed? Open an issue with a reproducible test page. A page that reliably flags Fortress is the most valuable thing you can send. It becomes the next patch. Two house rules: every capability claim ships with a command that reproduces it, and every limit is written down. **The word \"undetectable\" stays out of the repo.**\n\n### License\n\nBSD-3-Clause for the Fortress patches and tooling (matching Chromium). Chromium and the bundled fonts retain their own licenses; see [LICENSE](LICENSE) and [NOTICE](NOTICE). The patch series is published, so you can audit and rebuild the engine yourself.\n\n---\n\n\u003Cdiv align=\"center\">\n\n### What's next\n\n\u003C\u002Fdiv>\n\n> [!IMPORTANT]\n> **`v2`: MaskConfig runtime personas.** An **IPC-delivered, seed-driven persona graph** feeding a process-global C++ `MaskConfig` singleton: **thousands of coherence-invariant fingerprints from a single binary**, **per-`BrowserContext` identity isolation**, and **zero command-line footprint**. The rest is on the [roadmap](#reference).\n\n\u003Cdiv align=\"center\">\n\n### Staying current\n\nDetection keeps moving, so a stealth engine is only as good as its last rebase. Fortress tracks the latest Chromium monthly, re-runs the full gauntlet, and ships a patch whenever a detector finds a new tell, so what you run keeps matching what a real Chrome install looks like. [Watch the releases](https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress\u002Freleases) to follow the `v2` MaskConfig work, or [star the repo](https:\u002F\u002Fgithub.com\u002Ftiliondev\u002Ffortress\u002Fstargazers) if it's useful to you.\n\n\u003Ca href=\"https:\u002F\u002Fwww.star-history.com\u002F?repos=tiliondev%2Ffortress&type=date&legend=top-left\">\n \u003Cimg alt=\"Star History Chart\" src=\"docs\u002Fassets\u002Fstar-history.png\" width=\"720\" \u002F>\n\u003C\u002Fa>\n\n\u003Cbr\u002F>\n\n\u003Cem>Stealth you can read, rebuild, and run yourself.\u003C\u002Fem>\n\n\u003C\u002Fdiv>\n","Fortress 是一个专为反爬对抗设计的隐身 Chromium 浏览器引擎，通过在 Chromium C++ 层深度修补指纹特征（如 WebGL、TLS、JavaScript 代理等），使自动化浏览器行为在 Bot 检测系统中呈现为真实用户。核心能力包括零头显漏检（CreepJS 0%）、Cloudflare Turnstile 等主流防护绕过、与 Playwright\u002FPuppeteer 无缝集成（仅需一行 CDP 地址替换），并支持每月上游同步与可复现构建。适用于需要高稳定性的网页数据采集、AI 网络代理、自动化测试及风控对抗等场景。","2026-07-09 02:30:07","CREATED_QUERY"]