Fortress——一个隐身 Chromium 引擎,让你的自动化代理不再被封锁
英文标题:🔶 Show HN: Fortress – a stealth Chromium so your agents stop getting blocked
Fortress 是一个通过在 Chromium C++ 层面修补浏览器指纹的隐身引擎,可让爬虫与 AI 代理仅需一行代码改动即可避免被封锁。它通过了 CreepJS、Sannysoft、BrowserScan 以及实时 Cloudflare Turnstile 的检测,被判定为普通用户。开发者可直接将现有 Playwright 或 Puppeteer 通过 CDP 连接到 Fortress,无需修改业务逻辑,也无需额外 JS 隐身补丁。项目提供 Python/Node SDK、Docker 镜像和预编译二进制文件,并支持每月上游同步和可重现构建,对反爬对抗和代理浏览场景具有实用价值。
1.4 MB
958.3 KB
244.3 KB
368.7 KB
125.4 KB
164.2 KB
203.9 KB
163.7 KB
479.6 KB
中文
统御一切的浏览器引擎
隐身 Chromium 引擎
Fortress 是一个隐身 Chromium 引擎,只需改动一行代码,就能让你的爬虫和浏览器代理不再被封锁。 机器人检测器通过读取浏览器指纹来识别自动化行为;Fortress 在 Chromium 的 C++ 内部修正了指纹,因此浏览器呈现为普通的 Chrome 安装。爬虫任务可以顺利完成,代理能抵达它们被派往的页面,而 CreepJS、Sannysoft、BrowserScan 以及实时的 Cloudflare Turnstile 都会将其视为人类。将现有的 Playwright 或 Puppeteer 通过 CDP 指向 Fortress,其他代码无需任何改动。
Blink · V8 · BoringSSL 内部修补 · ANGLE / D3D11 支持的 WebGL · JA3/JA4 一致的 TLS · 每月上游同步 · 可重现、经挑战把关的发布
| 单层面 C++ 补丁 | CreepJS 无头/隐身 | 跨所有上下文 | 开放引擎,可自行重建 |
|---|
未经编辑的 Fortress 二进制在真实窗口中的捕捉:它通过了实时 Cloudflare 挑战,让 bot.sannysoft.com 全部变绿,随后 BrowserScan 显示“Normal”。可用 tools/gauntlet.py 重现。
每一个伪造的 getter 都是 C++ getter:toString 返回 [native code],跨主框架、iframe 和 Web Worker 保持一致。 |
类似 nodriver 的原始 CDP 连接于 :9222,没有 Runtime.enable 泄露。保留 Playwright、Puppeteer 或任何 CDP 客户端;只换浏览器,不改代码。 |
CreepJS 上实现 0% 无头检测痕迹;Sannysoft、BrowserScan 和实时 Cloudflare Turnstile 均通过,全部表现为普通 Chrome 安装。 |
|---|
patches/ 中包含 34 个小型单一用途差异文件。一分钟即可阅读一个;一个脚本即可重建引擎。 | 真实的 V8、Blink 和 BoringSSL 让引擎、用户代理和 JA3/JA4 TLS 特征保持一致:在匹配的堆栈上呈现 Windows 设备身份。 | 一个二进制文件,一致核验过的 Windows 身份;通过 --uxr-* 开关可覆盖任何表层:GPU、屏幕、时区、硬件、Client-Hints。 |
🆕 新特性 — 151.0.7908.0 · 引擎刷新
已发布。 每次启动保持一致的身份,更加强固。
- 按区域键盘(QWERTY / QWERTZ / AZERTY)· 按身份的媒体设备 · 完整的交付等价一致性检查。
- 平台 ↔ GPU ↔ 时区 ↔ 语言 ↔ 声音 ↔ 键盘作为一个连贯的真实设备移动——在每次启动时,从单个二进制文件中呈现。
- 原生代码等价(
toString()仍为[native code]),跨领域一致(主线程 / worker / iframe)——一致性被编译进浏览器,而非通过 JavaScript 修补。
pip install -U tilion-fortress # 或者: docker run --rm -p 9222:9222 tilion/fortress:latest
Fortress 是一个 Chromium 分支,从引擎内部伪装浏览器指纹。机器人检测器所读取的表层(canvas、WebGL、音频、字体、navigator 以及大约三十多个其他属性)均在 Chromium 的 C++ 中修正,其上没有任何可被页面捕获的 JavaScript 补丁层。
它作为普通的浏览器二进制文件发布,并会暴露一个 CDP 端点。将 Playwright、Puppeteer 或任何 CDP 客户端指向它,你现有的自动化程序即可不变运行。
JavaScript 隐身补丁会留下页面可以发现的额外层:.toString() 会显示覆盖层的源代码,而从 iframe 或 worker 中重新获取相同原语则会绕过该层。Fortress 则在引擎层面修正这些表层,因此 navigator.vendor 会解析为真正的 C++ getter,报告 [native code],并且在每个领域中都读取到相同的值。页面检查自身时看到的是原生的 Chromium。这就是你的自动化程序能够通过以往会被标记的检测,并且任何剩余的拦截只能追溯到你的代理和行为的根本原因。为什么要在引擎层面修补而不在页面层面,详细解释了完整的检测机制。
python from tilion_fortress import Fortress from playwright.sync_api import sync_playwright
with Fortress() as f: # 在 CDP 端点上启动隐身引擎 with sync_playwright() as p: browser = p.chromium.connect_over_cdp(f.cdp_url) page = browser.new_page() page.goto("https://bot.sannysoft.com") page.screenshot(path="all-green.png")
javascript import { Fortress } from "tilion-fortress"; import { chromium } from "playwright";
const f = await Fortress.launch(); // 在 CDP 端点上启动隐身引擎 const browser = await chromium.connectOverCDP(f.cdpUrl); const page = await browser.newPage(); await page.goto("https://browserscan.net"); await browser.close(); await f.close();
一个循环,全部为真实捕获:通过 CreepJS / Sannysoft / BrowserScan / rebrowser → 通过 CDP 抓取结构化数据 → 在 aa.com · lowes · macys · kohls(同一住宅 IP)上通过 Akamai。
真实抓取,完全无头
通过 CDP 驱动的 Fortress 引擎的未经编辑的捕获。无隐身插件,无 JS 补丁:指纹在二进制中修正。使用 examples/scrape_demos.py 可重现其中任何一项。
结构化提取:记录在读取每个项目时构建为类型化 JSON。

自动翻页:3 页共 30 条 quote。 | 
深层详情爬取:UPC · 价格 · 税费 · 库存 · 评论。
---|---
Python / Node: 预编译的原生二进制文件自动获取(Linux x64 及 Windows x64),SHA-256 验证
pip install tilion-fortress npm install tilion-fortress
通过 Docker 在任何操作系统上运行:暴露原始 CDP 于 :9222 (拉取约 302 MB / 磁盘占用 851 MB,去壳单层镜像)
docker run --rm -p 9222:9222 tilion/fortress:latest
便携包(解压即用,类似 Chromium snapshot)
tar xzf tilion-fortress-linux-x64.tar.gz # Linux ./tilion-fortress/tilion --headless=new --remote-debugging-port=9222 --user-data-dir=/tmp/p
Debian / Ubuntu
sudo apt install ./tilion-fortress_151.0.7908.0_amd64.deb && tilion https://example.com
提示
SDK 同时提供编译后的构建产物和 patches/,因此你可以自行重建引擎,并根据源代码验证每一项表层修正。下载时会依据发布版的 SHA256SUMS 自动进行 SHA-256 验证。
Fortress 提供两个 Chromium 基础——在融入真实环境与最新版本之间按需选择:
| 渠道 | Chromium | 何时使用 |
|---|---|---|
stable(默认) |
149 | 推荐——与大多数真实用户使用的 Chrome 版本一致,最能融入环境 |
latest |
151 | 最新引擎(报告的版本略领先于稳定版) |
python Fortress().start() # Python: 默认 stable (149) Fortress(channel="latest").start() # 选择 151
javascript await Fortress.launch(); // Node: 默认 stable (149) await Fortress.launch({ channel: "latest" });
docker docker run --rm -p 9222:9222 tilion/fortress:149 # 或 :151
或为任一 SDK 设置 FORTRESS_CHANNEL=latest
原生二进制文件:Linux x64(两个版本) + Windows x64(151);Windows-149 和 macOS 通过 Docker 镜像运行。
融入你的 AI 代理
Fortress 是供代理驱动的浏览器:在 :9222 上提供原始 CDP,无需配置任何隐身插件。有两种接入方式。
选项 1:在聊天助手中预加载。 一键操作;助手会读取我们的 AGENTS.md 并引导你完成整个设置:
English / 原文
One browser engine to rule them all
Stealth Chromium engine
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.
Blink · V8 · BoringSSL patched in-tree · ANGLE / D3D11 -backed WebGL · JA3/JA4-coherent TLS · monthly upstream rebase · reproducible, gauntlet-gated releases
single-surface
C++ patches | CreepJS
headless / stealth | across every
realm | open engine,
rebuild it yourself
---|---|---|---
Unedited capture of the Fortress binary in a real window: it clears a liveCloudflare challenge, turns bot.sannysoft.com all green, then reads BrowserScan “Normal”. Reproduce with tools/gauntlet.py.
Every spoofed getter is a C++ getter: toString returns [native code], realm-invariant across main frame, iframes, and Web Workers. |
nodriver-style raw CDP on :9222, with no Runtime.enable leak. Keep Playwright, Puppeteer, or any CDP client; swap the browser, keep your code. |
0% headless on CreepJS; Sannysoft, BrowserScan, and live Cloudflare Turnstile cleared, all as a stock Chrome install. |
|---|---|---|
34 small single-purpose diffs in patches/. Read one in a minute; rebuild the engine with one script. |
Real V8, Blink, and BoringSSL keep engine, user-agent, and JA3/JA4 TLS shape in agreement: a Windows persona on a matching stack. | One binary, a coherence-checked Windows identity; --uxr-* switches override any surface: GPU, screen, timezone, hardware, Client-Hints. |
🆕 What's new — 151.0.7908.0 · Engine Refresh
Released. Per-launch coherent personas, hardened.
-
Per-locale keyboard (QWERTY / QWERTZ / AZERTY) · per-persona media devices · a full delivery-parity coherence pass.
-
Platform ↔ GPU ↔ timezone ↔ language ↔ voices ↔ keyboard move as one coherent real device — on every launch, from a single binary.
-
Native-code parity (
toString()stays[native code]), realm-invariant (main / worker / iframe) — coherence compiled into the browser, not patched in JavaScript.pip install -U tilion-fortress # or: docker run --rm -p 9222:9222 tilion/fortress:latest
Fortress 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.
It 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.
A 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 covers the detection mechanics in full.
from tilion_fortress import Fortress
from playwright.sync_api import sync_playwright
with Fortress() as f: # launches the stealth engine on a CDP endpoint
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(f.cdp_url)
page = browser.new_page()
page.goto("https://bot.sannysoft.com")
page.screenshot(path="all-green.png")
import { Fortress } from "tilion-fortress";
import { chromium } from "playwright";
const f = await Fortress.launch(); // stealth engine on a CDP endpoint
const browser = await chromium.connectOverCDP(f.cdpUrl);
const page = await browser.newPage();
await page.goto("https://browserscan.net");
await browser.close();
await f.close();
One loop, all real captures: passes CreepJS / Sannysoft / BrowserScan / rebrowser → scrapes structured data over CDP → clears Akamai on aa.com · lowes · macys · kohls (same residential IP).
Real scraping, fully headless
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 examples/scrape_demos.py.
Structured extraction : records build into typed JSON as each item is read.

Auto-pagination : 30 quotes across 3 pages. | 
Deep detail crawl : UPC · price · tax · stock · reviews.
---|---
# Python / Node: prebuilt native binary auto-fetched (Linux x64 & Windows x64), SHA-256 verified
pip install tilion-fortress
npm install tilion-fortress
# Any OS via Docker: raw CDP on :9222 (~302 MB pull / 851 MB on disk, stripped single-layer)
docker run --rm -p 9222:9222 tilion/fortress:latest
# Portable bundle (extract-and-run, like a Chromium snapshot)
tar xzf tilion-fortress-linux-x64.tar.gz # Linux
./tilion-fortress/tilion --headless=new --remote-debugging-port=9222 --user-data-dir=/tmp/p
# Debian / Ubuntu
sudo apt install ./tilion-fortress_151.0.7908.0_amd64.deb && tilion https://example.com
Tip
The SDK ships the compiled build pluspatches/, 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.
Fortress ships on two Chromium bases — pick your trade-off between blend-in and currency:
| Channel | Chromium | When to use |
|---|---|---|
stable (default) |
149 | Recommended — matches the Chrome version the mass of real users run, so it blends in best |
latest |
151 | Newest engine (reports a version slightly ahead of stable) |
Fortress().start() # Python: stable (149) by default
Fortress(channel="latest").start() # opt into 151
await Fortress.launch(); // Node: stable (149) by default
await Fortress.launch({ channel: "latest" });
docker run --rm -p 9222:9222 tilion/fortress:149 # or :151
# or set FORTRESS_CHANNEL=latest for either SDK
Native binaries: Linux x64 (both versions) + Windows x64 (151); Windows-149 and macOS run via the Docker image.
Drop it into your AI agent
Fortress is the browser your agent drives: raw CDP on :9222, no stealth plugins to wire up. There are two ways in.
Option 1: open it pre-loaded in a chat assistant. One click; it reads our AGENTS.md and walks you through the whole setup:
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 condensed. Paste it into Cursor, Claude Code, Copilot, ChatGPT, or any agent and it takes it from there:
You're setting up Fortress, an open-source STEALTH Chromium engine, for browser automation.
It corrects the browser fingerprint (canvas, WebGL, audio, fonts, navigator, +30 more) in Chromium's
C++ and exposes raw CDP on http://localhost:9222, a drop-in for Playwright/Puppeteer. Do NOT add
puppeteer-stealth or any JS fingerprint patching (it self-reveals and undoes Fortress).
LAUNCH (pick one; all expose CDP on http://localhost:9222):
Docker: docker run -d --rm -p 9222:9222 tilion/fortress:latest
Python: pip install tilion-fortress then from tilion_fortress import Fortress; f=Fortress(); f.start()
Node: npm install tilion-fortress then import {Fortress} from "tilion-fortress"; const f=await Fortress.launch()
CONNECT (keep my existing automation code):
Playwright(py): browser = p.chromium.connect_over_cdp("http://localhost:9222")
Playwright(js): const browser = await chromium.connectOverCDP("http://localhost:9222")
Puppeteer(js): const browser = await puppeteer.connect({ browserURL: "http://localhost:9222" })
browser-use / Crawl4AI / Stagehand / LangChain: point their CDP endpoint at http://localhost:9222
PERSONA (optional; default is a coherent Windows identity). Override any surface with --uxr-* flags:
--uxr-timezone=America/New_York --uxr-hw-concurrency=16 --uxr-languages=en-US,en
RULES:
1) Drive over raw CDP (:9222); don't spawn chromedriver.
2) Never pass --user-agent (use --uxr-ua-*); it desyncs UA vs UA-Client-Hints.
3) No puppeteer-stealth / undetected-chromedriver / JS fingerprint patches.
4) Blocked ~90% = my IP (datacenter), not the fingerprint. Use a residential/mobile proxy, then retry.
Now walk me through launching Fortress and wiring my automation to it.
Full guide: https://github.com/tiliondev/fortress/blob/main/AGENTS.md
The Fortress MCP — stealth browsing as agent tools Beta
Raw CDP is for code you write. The Fortress MCP is for agents that call tools : a Model Context Protocol 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.
Real, dated run againststockx.com (PerimeterX). A stock browser gets HTTP 403 — “Access denied” ; an agent with the Fortress MCP returns clean JSON — same site, same prompt. Reproduce it from the framework repo.
Two runners — pick one. npx needs Python on PATH; pip installs it directly:
pip install "tilion[mcp]" # command: tilion-mcp
# —or, zero-install—
npx -y tilion-mcp # auto-runs the server via uv (no global install)
Claude Desktop — Settings → Developer → Edit Config (claude_desktop_config.json):
{ "mcpServers": { "fortress": { "command": "tilion-mcp" } } }
Prefer npx? Use "command": "npx", "args": ["-y", "tilion-mcp"]. Restart Claude, and the fortress tools appear.
Claude Code (CLI) — one line:
claude mcp add fortress -- tilion-mcp # or: claude mcp add fortress -- npx -y tilion-mcp
Cursor (~/.cursor/mcp.json) · Cline / Windsurf (VS Code → MCP servers) — same block:
{ "mcpServers": { "fortress": { "command": "tilion-mcp" } } }
Then just ask your agent — “get the price off this StockX page” — and it calls fetch_protected_page on its own.
| tools
---|---
Get blocked pages | fetch_protected_page · read_page · get_page_html · search_web
Structured data | extract_page (schema-aware) · extract_document (PDF/DOCX/XLSX)
Whole sites | crawl_site (auto-SPA) · recon_site_apis (find the private JSON API)
Drive a page | page_elements · click_button · fill_field · press_key · wait_for · evaluate_js
Multi-step flows | run_browser_task (login, paginate, infinite-scroll, checkout, …)
Capture / auth | screenshot_page · save_page · download_file · save_profile / load_profile
Bring your own | get_stealth_cdp_endpoint → a CDP url for Playwright / Puppeteer / browser-use
Tools 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.
→ Full 29-tool table, benchmarks, and the agent skill: mcp/
Why patch the engine, not the page
The 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:
| The tell | Why it catches a JS spoof |
|---|---|
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. |
Descriptor and hasOwnProperty |
getOwnPropertyDescriptor exposes redefined props, and hasOwnProperty('toString') returns true on a tampered function where a native one returns false. |
failsTypeError |
Native getters throw a specific TypeError on the wrong this; a naive shim stays quiet, and the silence is the signal. |
Realm 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:
const iframe = document.createElement('iframe'); document.body.appendChild(iframe);
const realToString = iframe.contentWindow.Function.prototype.toString;
realToString.call(navigator.__lookupGetter__('vendor')); // returns your source code. Caught.
Your 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.
Fortress 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.
The three layers of bot detection, and where Fortress fits
Modern anti-bots (Cloudflare, DataDome, Kasada, HUMAN, Akamai) read three structurally different surfaces, in three separate places. One tool rarely fixes all three:
Fortress 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.
| Stock Playwright | puppeteer-extra-stealth | undetected-chromedriver | Camoufox | CloakBrowser | Fortress
---|---|---|---|---|---|---
Spoof layer | none | JS injection | CDP/config patch | C++ engine | C++ engine | C++ engine
toString yields [native code] | n/a | | n/a |
|
|
Survives realm re-acquisition (iframe/worker) | |
|
|
|
|
No Runtime.enable leak | |
|
|
|
|
Engine = Chrome / V8 (majority traffic) | |
|
|
Firefox |
|
Coherent Chromium TLS shape | |
|
|
Firefox |
|
Fully open-source engine | |
|
|
|
latest major paywalled |
Published, auditable patch series | n/a | n/a | n/a | |
binary only |
Reproducible from-source build | n/a | n/a | n/a | |
|
States its own limits | n/a | n/a | n/a | |
|
Fortress builds on real prior art: fingerprint-chromium, ChromiumFish, 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.
Fortress 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/ 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.
Proof: live-detector results
Reproduce any row withtools/gauntlet.py --bundle ./tilion-fortress. Verified against live detectors; re-run dated in docs/GAUNTLET_RESULTS.md.
The 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.
--uxr-platform / --uxr-ua-platform / --uxr-ua-os / --uxr-ua-arch / --uxr-ua-bitness
--uxr-ua-platform-version / --uxr-ua-brand / --uxr-hw-concurrency / --uxr-device-memory
--uxr-webgl-vendor / --uxr-webgl-renderer / --uxr-webgl-fullparams
--uxr-canvas-seed / --uxr-audio-seed / --uxr-timezone / --uxr-languages
--uxr-screen-width / --uxr-screen-height / --uxr-webrtc-policy=disable_non_proxied_udp
| Env var | Purpose |
|---|---|
TILION_NO_DEFAULTS=1 |
Skip the default persona (bare launch) |
TILION_TZ / TILION_LANG |
Quick timezone / language override |
Note
The persona rides on the command line today. The --uxr-* switches are world-readable via /proc/<pid>/cmdline, 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).
Fortress exposes raw CDP on :9222, so it drops in under anything that speaks Playwright, Puppeteer, or CDP.
| Framework | Connect via |
|---|---|
| browser-use (~70k stars) | cdp_url="http://localhost:9222" |
| Crawl4AI (~58k stars) | CDP endpoint |
| Stagehand (~21k stars) | connectOverCDP |
| LangChain Playwright toolkit | Playwright CDP |
| Playwright / Puppeteer (Python & JS) | connect_over_cdp / connect |
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp("http://localhost:9222") # Fortress under the hood
export CHROMIUM_VERSION=$(cat CHROMIUM_VERSION)
build/build.sh # depot_tools, sync the tag, apply patches, gn gen, ninja
build/rebase-monthly.sh 152.0.XXXX.0 # bump + 3-way apply + rebuild + gauntlet-gate
Output: out/Fortress/chrome. The fork is 34 small single-surface patches (patches/), so most re-apply cleanly across upstream releases; the gauntlet then gates the release on any regression.
| Platform | Status |
|---|---|
| Linux x64 (native) · Windows x64 (native) · any OS via Docker | |
Code-signed installers · macOS .app · linux/arm64 |
in progress |
Fortress ships from four official channels. Treat anything else as untrusted:
Verify a download. Every release ships SHA256SUMS, and the pip/npm SDKs run this for you on install:
BASE=https://github.com/tiliondev/fortress/releases/download/v151.0.7908.0
curl -LO $BASE/tilion-fortress-linux-x64.tar.gz
curl -Ls $BASE/SHA256SUMS | sha256sum -c --ignore-missing # -> OK
Verify the Docker image by digest (not just the tag):
docker pull tilion/fortress:151.0.7908.0
docker inspect --format '{{index .RepoDigests 0}}' tilion/fortress:151.0.7908.0
# compare the printed sha256:... against the digest in the GitHub Release notes
Or trust nothing and rebuild it. The whole fork is 34 readable patches in patches/; build/build.sh reproduces the binary from Chromium source, so you can diff what you built against what we ship.
Troubleshooting
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.
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.
macOS pulls a Docker image. Native Linux + Windows binaries ship today; macOS still runs Fortress through the official Docker image (tilion/fortress). Install Docker Desktop, or run on Linux/Windows x64 for the native binary.
The persona shows up in/proc/<pid>/cmdline. 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.
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.
FAQ
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.
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.
Why not just use puppeteer-stealth or undetected-chromedriver? They patch the JS/CDP 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.")
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.
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.
Roadmap
Repo layout
patches/ 34 per-surface C++ patches (+ series), the source of truth for the fork
build/ args.gn, build.sh, apply-patches.sh, rebase-monthly.sh, windows/, macos/
packaging/ tilion launcher, fonts.conf, Dockerfile, .deb + bundle builders
fonts/ 33 metric-compatible Windows-named fonts (incl. color emoji)
sdk/ python + node (tilion-fortress) prebuilt-binary SDKs
tools/ gauntlet.py, the CreepJS / Sannysoft / BrowserScan CI gate
docs/ GAUNTLET_RESULTS, BUILD_NATIVE, BENCHMARK
Found 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.
BSD-3-Clause for the Fortress patches and tooling (matching Chromium). Chromium and the bundled fonts retain their own licenses; see LICENSE and NOTICE. The patch series is published, so you can audit and rebuild the engine yourself.
Important
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.
Detection 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 to follow the v2 MaskConfig work, or star the repo if it's useful to you.
Stealth you can read, rebuild, and run yourself.








