PluginBench
MCP Server
Active

io.github.tiliondev/fortress MCP Server

io.github.tiliondev/fortress

Stealth Chromium engine that bypasses bot detection by spoofing fingerprints at the C++ level, not JavaScript.

What is the io.github.tiliondev/fortress MCP server?

Fortress is a stealth Chromium browser engine that prevents AI agents and scrapers from being blocked by bot detectors like Cloudflare, DataDome, and Akamai. It corrects browser fingerprints inside the engine's C++ code rather than via JavaScript patches, making automation indistinguishable from real Chrome. Available as a drop-in CDP endpoint for Playwright, Puppeteer, and other automation tools.

Fortress is a hardened Chromium fork that stops web scrapers and browser agents from getting blocked by anti-bot systems. Instead of patching fingerprints in JavaScript (which pages can detect), Fortress corrects the fingerprint surfaces—canvas, WebGL, navigator, fonts, and 30+ others—directly in Chromium's C++. This makes the browser appear as stock Chrome to detectors like CreepJS, Sannysoft, BrowserScan, and live Cloudflare Turnstile. You point your existing Playwright or Puppeteer code at Fortress over CDP; nothing else changes.

How to install io.github.tiliondev/fortress

Copy-paste configuration for popular MCP clients.

transport: stdio
Config generated by PluginBench — verify against the source before use.
Environment / auth
  • TILION_MCP_PREWARM

    Boot the browser at startup so the first tool call is instant (default 1).

  • TILION_MCP_HEADLESS

    Set 0 to show a visible browser window (default 1).

  • TILION_ALLOW_PRIVATE_EGRESS

    Set 1 to allow tools to reach localhost / private IPs (SSRF guard off).

  • TILION_BASE_URL

    Run browsers on a hosted Tilion server instead of locally.

  • TILION_API_KEY
    secret

    Bearer key for the hosted Tilion server (cloud mode only).

Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "fortress": {
      "command": "uvx",
      "args": [
        "tilion",
        "tilion-mcp"
      ],
      "env": {
        "TILION_MCP_PREWARM": "<YOUR_TILION_MCP_PREWARM>",
        "TILION_MCP_HEADLESS": "<YOUR_TILION_MCP_HEADLESS>",
        "TILION_ALLOW_PRIVATE_EGRESS": "<YOUR_TILION_ALLOW_PRIVATE_EGRESS>",
        "TILION_BASE_URL": "<YOUR_TILION_BASE_URL>",
        "TILION_API_KEY": "<YOUR_TILION_API_KEY>"
      }
    }
  }
}
Cursor
~/.cursor/mcp.json
{
  "mcpServers": {
    "fortress": {
      "command": "uvx",
      "args": [
        "tilion",
        "tilion-mcp"
      ],
      "env": {
        "TILION_MCP_PREWARM": "<YOUR_TILION_MCP_PREWARM>",
        "TILION_MCP_HEADLESS": "<YOUR_TILION_MCP_HEADLESS>",
        "TILION_ALLOW_PRIVATE_EGRESS": "<YOUR_TILION_ALLOW_PRIVATE_EGRESS>",
        "TILION_BASE_URL": "<YOUR_TILION_BASE_URL>",
        "TILION_API_KEY": "<YOUR_TILION_API_KEY>"
      }
    }
  }
}
Windsurf
~/.codeium/windsurf/mcp_config.json
{
  "mcpServers": {
    "fortress": {
      "command": "uvx",
      "args": [
        "tilion",
        "tilion-mcp"
      ],
      "env": {
        "TILION_MCP_PREWARM": "<YOUR_TILION_MCP_PREWARM>",
        "TILION_MCP_HEADLESS": "<YOUR_TILION_MCP_HEADLESS>",
        "TILION_ALLOW_PRIVATE_EGRESS": "<YOUR_TILION_ALLOW_PRIVATE_EGRESS>",
        "TILION_BASE_URL": "<YOUR_TILION_BASE_URL>",
        "TILION_API_KEY": "<YOUR_TILION_API_KEY>"
      }
    }
  }
}
VS Code
.vscode/mcp.json
{
  "servers": {
    "fortress": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "tilion",
        "tilion-mcp"
      ],
      "env": {
        "TILION_MCP_PREWARM": "<YOUR_TILION_MCP_PREWARM>",
        "TILION_MCP_HEADLESS": "<YOUR_TILION_MCP_HEADLESS>",
        "TILION_ALLOW_PRIVATE_EGRESS": "<YOUR_TILION_ALLOW_PRIVATE_EGRESS>",
        "TILION_BASE_URL": "<YOUR_TILION_BASE_URL>",
        "TILION_API_KEY": "<YOUR_TILION_API_KEY>"
      }
    }
  }
}
Claude Code
claude mcp add fortress --env TILION_MCP_PREWARM=<YOUR_TILION_MCP_PREWARM> --env TILION_MCP_HEADLESS=<YOUR_TILION_MCP_HEADLESS> --env TILION_ALLOW_PRIVATE_EGRESS=<YOUR_TILION_ALLOW_PRIVATE_EGRESS> --env TILION_BASE_URL=<YOUR_TILION_BASE_URL> --env TILION_API_KEY=<YOUR_TILION_API_KEY> -- uvx tilion tilion-mcp

Tools & capabilities

Tools this server exposes to the agent.

  • fetch_pageFetch and render web pages, bypassing Cloudflare, DataDome, CAPTCHA, and other bot detection systems
  • extract_dataExtract structured data from rendered pages into typed JSON records
  • auto_paginateAutomatically navigate and scrape across multiple pages of paginated content
  • deep_crawlDeep-crawl product detail pages and extract nested information like UPC, price, tax, stock, and reviews
  • screenshotCapture screenshots of rendered pages for verification or documentation
  • navigateNavigate to URLs and wait for page load with configurable timeouts
  • evaluate_jsExecute JavaScript in the page context and retrieve results
  • get_cookiesRetrieve cookies from the current page session
  • set_cookiesSet cookies for the current page session
  • get_local_storageAccess localStorage data from the current page
  • set_local_storageSet localStorage data for the current page
  • wait_for_selectorWait for DOM elements matching a selector to appear
  • clickClick elements on the page
  • typeType text into input fields
  • get_textExtract text content from page elements
  • get_htmlRetrieve HTML content of the page or specific elements
  • get_attributeGet attribute values from DOM elements
  • set_viewportConfigure viewport size and device emulation
  • set_user_agentOverride the user agent string
  • set_timezoneSet the browser timezone

Use cases

  • Scrape e-commerce sites protected by Akamai, Cloudflare, or DataDome without getting blocked
  • Extract structured product data (price, stock, reviews) from retail websites at scale
  • Automate form filling and multi-step workflows on bot-protected sites
  • Monitor competitor pricing and inventory across protected retail platforms
  • Gather market research data from sites with aggressive anti-scraping measures

io.github.tiliondev/fortress MCP server FAQ

What is the Fortress MCP server?

Fortress is an MCP server that exposes 29 stealth-browser tools for AI agents. It wraps the Fortress Chromium engine and allows Claude, Cursor, and other AI agents to fetch pages behind Cloudflare/DataDome/CAPTCHA, render them, extract clean data, and navigate multi-page content—all without triggering bot detection.

How do I install Fortress?

Install via pip: `pip install tilion-fortress` (Python) or `npm install tilion-fortress` (Node.js). Prebuilt binaries for Linux x64 and Windows x64 are auto-fetched and SHA-256 verified. Alternatively, run via Docker: `docker run --rm -p 9222:9222 tilion/fortress:latest`.

How do I use Fortress with Cursor or Claude?

Launch Fortress (e.g., `docker run -p 9222:9222 tilion/fortress:latest`), then configure your MCP server to connect to the CDP endpoint at `http://localhost:9222`. The 29 tools become available to your AI agent for browser automation. See AGENTS.md in the repository for step-by-step setup.

Is Fortress free?

Yes. Fortress is open-source under the BSD-3 license. You can use it freely, rebuild it from source, and verify all 34 C++ patches yourself.

What authentication or API keys does Fortress require?

Fortress requires no API keys or authentication. It runs locally as a Chromium binary exposing a CDP endpoint. You control it entirely; no external services are needed.

How does Fortress differ from puppeteer-stealth or other JavaScript patches?

JavaScript stealth patches sit on top of the browser and can be detected by pages inspecting `.toString()` or re-grabbing primitives from iframes/workers. Fortress corrects fingerprints in Chromium's C++ instead, so `navigator.vendor` and other surfaces resolve to real native code, making detection impossible. This is why it clears live Cloudflare Turnstile and Akamai Bot Manager.

README (reference)

Source of truth, from the repository.

<div align="center"> <img alt="Fortress" src="docs/assets/banner-fortress.png" width="100%">

One browser engine to rule them all

Stealth Chromium engine

Chromium pip npm Docker pulls Discord<br/> CreepJS Runtime.enable leak<br/> Copy for agent llms.txt<br/> MCP server npm tilion-mcp

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.

<sub>Blink · V8 · BoringSSL patched in-tree · ANGLE / D3D11-backed WebGL · JA3/JA4-coherent TLS · monthly upstream rebase · reproducible, gauntlet-gated releases</sub>

<table align="center"><tr> <td align="center" width="150"><h3>34</h3><sub>single-surface<br/>C++ patches</sub></td> <td align="center" width="150"><h3>0%</h3><sub>CreepJS<br/>headless / stealth</sub></td> <td align="center" width="160"><h3><code>[native&nbsp;code]</code></h3><sub>across every<br/>realm</sub></td> <td align="center" width="150"><h3>BSD-3</h3><sub>open engine,<br/>rebuild it yourself</sub></td> </tr></table> <p align="center"><img src="docs/assets/demo.gif" alt="Fortress clearing a live Cloudflare challenge, then passing sannysoft and BrowserScan" width="720"/></p>

<sub><i>Unedited capture of the Fortress binary in a real window: it clears a live <b>Cloudflare</b> challenge, turns <b>bot.sannysoft.com</b> all green, then reads <b>BrowserScan</b> “Normal”. Reproduce with <code>tools/gauntlet.py</code>.</i></sub>

</div> <table> <tr> <td width="33%" valign="top">

Native-code parity

Every spoofed getter is a C++ getter: toString returns [native code], realm-invariant across main frame, iframes, and Web Workers.

</td> <td width="33%" valign="top">

Drop-in CDP

nodriver-style raw CDP on :9222, with no Runtime.enable leak. Keep Playwright, Puppeteer, or any CDP client; swap the browser, keep your code.

</td> <td width="33%" valign="top">

Clears the gauntlet

0% headless on CreepJS; Sannysoft, BrowserScan, and live Cloudflare Turnstile cleared, all as a stock Chrome install.

</td> </tr> <tr> <td width="33%" valign="top">

Auditable patches

34 small single-purpose diffs in patches/. Read one in a minute; rebuild the engine with one script.

</td> <td width="33%" valign="top">

Coherent by construction

Real V8, Blink, and BoringSSL keep engine, user-agent, and JA3/JA4 TLS shape in agreement: a Windows persona on a matching stack.

</td> <td width="33%" valign="top">

Tunable persona

One binary, a coherence-checked Windows identity; --uxr-* switches override any surface: GPU, screen, timezone, hardware, Client-Hints.

</td> </tr> </table>

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

→ full release notes

Contents

What it is · Quick startwhat it is, install, first script, AI-agent setup
The Fortress MCP29 stealth-browser tools for AI agents (Beta)
Why patch the engine, not the pagethe self-revealing-JS thesis + the three detection layers
How Fortress comparesvs puppeteer-stealth · Camoufox · CloakBrowser · closed vendors
Proof: live-detector resultsCreepJS / Sannysoft / BrowserScan / Cloudflare, with screenshots
Configure the personathe --uxr-* fingerprint surface
Works with your stackbrowser-use · Crawl4AI · Stagehand · LangChain
Build & verifyreproduce from source, verify provenance
Referencetroubleshooting · FAQ · roadmap · repo layout

What it is

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();
<div align="center">

The 12-second tour

<img src="docs/assets/fortress-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."/>

<sub>One loop, all real captures: passes <b>CreepJS / Sannysoft / BrowserScan / rebrowser</b> → scrapes structured data over CDP → clears <b>Akamai</b> on aa.com · lowes · macys · kohls (same residential IP).</sub>

</div> <div align="center">

Real scraping, fully headless

<sub>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 <a href="examples/scrape_demos.py"><code>examples/scrape_demos.py</code></a>.</sub>

<img src="docs/assets/fortress-scrape-structured.gif" width="720" alt="Fortress extracting books.toscrape.com into typed JSON records live over CDP"/>

<sub><b>Structured extraction</b>: records build into typed JSON as each item is read.</sub>

<table><tr> <td align="center" width="50%"><img src="docs/assets/fortress-scrape-paginated.gif" width="358" alt="Fortress auto-paginating across pages of quotes.toscrape.com"/><br/><sub><b>Auto-pagination</b>: 30 quotes across 3 pages.</sub></td> <td align="center" width="50%"><img src="docs/assets/fortress-scrape-detail.gif" width="358" alt="Fortress deep-crawling a product detail page"/><br/><sub><b>Deep detail crawl</b>: UPC · price · tax · stock · reviews.</sub></td> </tr></table> </div> <div align="center">

Clears real Akamai — before / after

<img src="docs/assets/fortress-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."/>

<sub>Same residential IP, same site (<b>aa.com</b> · Akamai Bot Manager). A stock/headless browser gets <b>Access Denied</b> (Reference #); Fortress loads the real page and Akamai issues its <code>_abck</code> sensor cookie — the Bot Manager accepts it as a real browser. The variable is the <b>fingerprint</b>, not the IP.</sub>

<table><tr> <td align="center" width="33%"><img src="docs/assets/fortress-akamai-lowes.gif" width="250" alt="Fortress clearing Akamai on lowes.com"/><br/><sub><b>lowes.com</b> · blocked → cleared</sub></td> <td align="center" width="33%"><img src="docs/assets/fortress-akamai-macys.gif" width="250" alt="Fortress clearing Akamai on macys.com"/><br/><sub><b>macys.com</b> · blocked → cleared</sub></td> <td align="center" width="33%"><img src="docs/assets/fortress-akamai-kohls.gif" width="250" alt="Fortress clearing Akamai on kohls.com"/><br/><sub><b>kohls.com</b> · blocked → cleared</sub></td> </tr></table>

<sub>Not a one-site fluke — same before/after on major Akamai-protected retailers, every run from the same residential IP.</sub>

</div>

Quick start

# 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 plus patches/, 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.

Versions

Fortress ships on two Chromium bases — pick your trade-off between blend-in and currency:

ChannelChromiumWhen to use
stable (default)149Recommended — matches the Chrome version the mass of real users run, so it blends in best
latest151Newest 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:

Ask ChatGPT Ask Claude Ask Gemini Copy for agent

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  <sub>Beta</sub>

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 freefetch_protected_page, extract_page, crawl_site, recon_site_apis, search_web, run_browser_task, save_profile, get_stealth_cdp_endpoint, and more.

<p align="center"><img src="mcp/demo.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"/></p>

<sub><i>Real, dated run against <b>stockx.com</b> (PerimeterX). A stock browser gets <b>HTTP 403 — “Access denied”</b>; an agent with the Fortress MCP returns clean JSON — same site, same prompt. Reproduce it from the framework repo.</i></sub>

Set it up in 30 seconds

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" } } }

<sub>Prefer npx? Use <code>"command": "npx", "args": ["-y", "tilion-mcp"]</code>. Restart Claude, and the <b>fortress</b> tools appear.</sub>

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.

What the agent gets

tools
Get blocked pagesfetch_protected_page · read_page · get_page_html · search_web
Structured dataextract_page (schema-aware) · extract_document (PDF/DOCX/XLSX)
Whole sitescrawl_site (auto-SPA) · recon_site_apis (find the private JSON API)
Drive a pagepage_elements · click_button · fill_field · press_key · wait_for · evaluate_js
Multi-step flowsrun_browser_task (login, paginate, infinite-scroll, checkout, …)
Capture / authscreenshot_page · save_page · download_file · save_profile / load_profile
Bring your ownget_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 tellWhy it catches a JS spoof
toString self-revealA native method stringifies to function get vendor() { [native code] }; an override stringifies to its own source, so one .toString() catches it.
Descriptor and hasOwnPropertygetOwnPropertyDescriptor exposes redefined props, and hasOwnProperty('toString') returns true on a tampered function where a native one returns false.
failsTypeErrorNative 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:

LayerThe tellsWhere the fix livesFortress
A: driver / binary artifactscdc_ ChromeDriver vars, WebDriver protocol surfaceDrive raw CDP, skip chromedriver<img src="docs/assets/icons/check.svg" width="15" alt="yes"> built to be driven this way
B: CDP side-effectsRuntime.enable leaks via sourceURL + init-script footprints, however clean the binary isThe control / CDP-client layer: hold back Runtime.enable, use Runtime.addBinding + isolated worlds<img src="docs/assets/icons/check.svg" width="15" alt="yes"> no leak (verified)
C: fingerprint surfacecanvas, WebGL, audio, fonts, navigator, across main frame, iframes, workersThe engine (C++), because JS overrides self-reveal<img src="docs/assets/icons/check.svg" width="15" alt="yes"> this is Fortress

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.


How Fortress compares

Stock Playwrightpuppeteer-extra-stealthundetected-chromedriverCamoufoxCloakBrowserFortress
Spoof layernoneJS injectionCDP/config patchC++ engineC++ engineC++ engine
toString yields [native code]n/a<img src="docs/assets/icons/x.svg" width="15" alt="no">n/a<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
Survives realm re-acquisition (iframe/worker)<img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
No Runtime.enable leak<img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/warn.svg" width="15" alt="partial"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
Engine = Chrome / V8 (majority traffic)<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/x.svg" width="15" alt="no"> Firefox<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
Coherent Chromium TLS shape<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/x.svg" width="15" alt="no"> Firefox<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
Fully open-source engine<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/warn.svg" width="15" alt="partial"> latest major paywalled<img src="docs/assets/icons/check.svg" width="15" alt="yes">
Published, auditable patch seriesn/an/an/a<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/warn.svg" width="15" alt="partial"> binary only<img src="docs/assets/icons/check.svg" width="15" alt="yes">
Reproducible from-source buildn/an/an/a<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/check.svg" width="15" alt="yes">
States its own limitsn/an/an/a<img src="docs/assets/icons/check.svg" width="15" alt="yes"><img src="docs/assets/icons/x.svg" width="15" alt="no"><img src="docs/assets/icons/check.svg" width="15" alt="yes">

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 with tools/gauntlet.py --bundle ./tilion-fortress. Verified against live detectors; re-run dated in docs/GAUNTLET_RESULTS.md.

SuiteStock ChromiumFortress
CreepJSflagged headless0% headless · 0% stealth, worker signals coherent
bot.sannysoft.comred rows0 failed · WebDriver Advanced passed · WebGL = NVIDIA RTX 3060 / ANGLE D3D11
browserscan.netbot detected“No bots detected, could be a human”
rebrowser bot-detectorRuntime.enable LEAKno leak · webdriver=false · clean init-scripts (raw CDP)
Cloudflare Turnstileblockedbypassed: a human click cleared a live challenge (headed, datacenter IP)
<details open><summary><b>Proof: real, unedited screenshots</b></summary> <br/> <img src="docs/assets/sannysoft_top.png" width="680"/>
BrowserScanCreepJSCloudflare
<img src="docs/assets/browserscan_top.png" width="240"/><img src="docs/assets/creepjs.png" width="240"/><img src="docs/assets/cloudflare_bypass.gif" width="240"/>
</details>

Configure the persona

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 varPurpose
TILION_NO_DEFAULTS=1Skip the default persona (bare launch)
TILION_TZ / TILION_LANGQuick 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).


Works with your stack

Fortress exposes raw CDP on :9222, so it drops in under anything that speaks Playwright, Puppeteer, or CDP.

FrameworkConnect via
browser-use (~70k stars)cdp_url="http://localhost:9222"
Crawl4AI (~58k stars)CDP endpoint
Stagehand (~21k stars)connectOverCDP
LangChain Playwright toolkitPlaywright 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

Build & verify

Reproduce from source

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.

PlatformStatus
Linux x64 (native) · Windows x64 (native) · any OS via Docker<img src="docs/assets/icons/check.svg" width="15" alt="yes"> shipping
Code-signed installers · macOS .app · linux/arm64in progress

Verify it's really ours

Fortress ships from four official channels. Treat anything else as untrusted:

Official source
Sourcegithub.com/tiliondev/fortress
Dockertilion/fortress
Pythontilion-fortress
Nodetilion-fortress

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.


Reference

<details><summary><b>Troubleshooting</b></summary> <br/>

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.

</details> <details><summary><b>FAQ</b></summary> <br/>

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.

</details> <details><summary><b>Roadmap</b></summary> <br/>
  • Runtime JSON config into a C++ MaskConfig (one binary, many coherent fingerprints, nothing on the command line)
  • First-party MCP server plus Puppeteer / raw-CDP SDKs (drop-in for AI agents)
  • Code-signed Windows .exe and macOS .app
  • linux/arm64 Docker image
  • Migrate patches/ to Brave-style chromium_src/ overrides
  • Published reCAPTCHA v3 / DataDome / Kasada benchmark rows (dated, reproducible)
</details> <details><summary><b>Repo layout</b></summary>
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
</details>

Contributing

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.

License

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.


<div align="center">

What's next

</div>

[!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.

<div align="center">

Staying current

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.

<em>Stealth you can read, rebuild, and run yourself.</em>

</div>

Related MCP servers

Control a real Chrome browser to complete any task: fill forms, extract data, book flights.

110k
Python
MIT
View repository →

Real-time global intelligence: markets, conflicts, country risk, energy, and infrastructure monitoring via 39 MCP tools.

83k
TypeScript
AGPL-3.0
View repository →

Netdata

Active

Real-time infrastructure monitoring with per-second metrics, ML-powered anomaly detection, and zero-configuration setup.

80k
Go
GPL-3.0
View repository →

Trending hip-hop artist momentum scores across four cultural dimensions.

79k
TypeScript
MIT
View repository →

AI orchestration platform with 100+ agents, swarm coordination, and self-learning memory for enterprise development.

68k
TypeScript
MIT
View repository →

Web scraping with stealth HTTP, real browsers, and Cloudflare bypass capabilities.

67k
Python
BSD-3-Clause
View repository →