tilion-fortress 151.0.7908

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +79 -0
  2. package/cli.js +16 -0
  3. package/index.js +148 -0
  4. package/package.json +15 -0
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/tiliondev/fortress/main/docs/assets/dockerhub-banner.png" width="100%" alt="Fortress — stealth Chromium engine">
3
+ </p>
4
+
5
+ <h1 align="center">tilion-fortress</h1>
6
+
7
+ <p align="center"><b>Drive the Fortress stealth Chromium engine with one line — no build, no Chromium source.</b></p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/tilion-fortress"><img src="https://img.shields.io/npm/v/tilion-fortress?logo=npm" alt="npm"></a>
11
+ <a href="https://www.npmjs.com/package/tilion-fortress"><img src="https://img.shields.io/node/v/tilion-fortress?logo=node.js&logoColor=white" alt="node"></a>
12
+ <a href="https://hub.docker.com/r/tilion/fortress"><img src="https://img.shields.io/docker/pulls/tilion/fortress?logo=docker&logoColor=white&label=docker%20pulls" alt="Docker Pulls"></a>
13
+ <a href="https://github.com/tiliondev/fortress"><img src="https://img.shields.io/github/stars/tiliondev/fortress?style=social" alt="Stars"></a>
14
+ <a href="https://github.com/tiliondev/fortress/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-blue" alt="License"></a>
15
+ </p>
16
+
17
+ ---
18
+
19
+ **Stop getting blocked — without `puppeteer-stealth`.** JavaScript stealth patches self-reveal: a detector checks whether a getter is native code and catches the override. Fortress compiles the fingerprint correction into Chromium's **C++**, so a page inspecting itself sees stock Chrome. It clears **CreepJS**, **Sannysoft**, **BrowserScan**, and live **Cloudflare** as a normal Chrome install.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install tilion-fortress
25
+ ```
26
+
27
+ On first launch it fetches the prebuilt Fortress binary for your platform from the official GitHub Release (SHA-256 verified) and caches it. No Chromium source, no compilation.
28
+
29
+ ## Quick start
30
+
31
+ ```js
32
+ import { Fortress } from "tilion-fortress";
33
+ import puppeteer from "puppeteer-core";
34
+
35
+ const f = await Fortress.launch(); // stealth engine on a CDP endpoint
36
+ const browser = await puppeteer.connect({ browserURL: f.cdpUrl });
37
+ const page = await browser.newPage();
38
+ await page.goto("https://bot.sannysoft.com");
39
+ await page.screenshot({ path: "all-green.png" });
40
+ await browser.close();
41
+ await f.close();
42
+ ```
43
+
44
+ Keep your existing Puppeteer / Playwright / CDP code — just point it at `f.cdpUrl`. Works the same under **browser-use**, **Crawl4AI**, **Stagehand**, and **LangChain**.
45
+
46
+ ## Verified against live detectors
47
+
48
+ | Suite | Result |
49
+ |---|---|
50
+ | **CreepJS** | 0% headless · 0% stealth |
51
+ | **bot.sannysoft.com** | 0 failed · all green · WebGL = NVIDIA RTX 3060 / ANGLE D3D11 |
52
+ | **browserscan.net** | "No bots detected, could be a human" |
53
+ | **rebrowser bot-detector** | no `Runtime.enable` leak · `webdriver=false` |
54
+ | **Cloudflare Turnstile** | cleared a live challenge |
55
+
56
+ ## Custom persona
57
+
58
+ The default persona is a coherent Windows identity. Override any surface:
59
+
60
+ ```js
61
+ const f = await Fortress.launch({
62
+ persona: { timezone: "America/Chicago", languages: "en-GB,en", hwConcurrency: 16 },
63
+ extraArgs: ["--window-size=1280,800"],
64
+ });
65
+ ```
66
+
67
+ ## Platform support
68
+
69
+ Linux x64 has a native prebuilt binary. On macOS / Windows the package transparently runs Fortress via the official Docker image (`tilion/fortress`) — Docker is the cross-OS vehicle until native Win/Mac builds ship.
70
+
71
+ > **Still blocked?** ~90% of the time it's your **IP**, not your fingerprint — datacenter ranges are flagged before any page script runs. Route egress through a residential or mobile proxy and retry.
72
+
73
+ ## Links
74
+
75
+ - **Source & docs:** https://github.com/tiliondev/fortress
76
+ - **Agent guide:** https://github.com/tiliondev/fortress/blob/main/AGENTS.md
77
+ - **Docker image:** https://hub.docker.com/r/tilion/fortress
78
+
79
+ BSD-3-Clause · reproducible from source · monthly Chromium rebase · **Blink · V8 · BoringSSL** patched in-tree.
package/cli.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // CLI: `tilion-fortress` — launch Fortress and print the CDP URL.
3
+ import { Fortress, VERSION } from "./index.js";
4
+
5
+ const args = process.argv.slice(2);
6
+ if (args.includes("--version")) { console.log(`tilion-fortress ${VERSION}`); process.exit(0); }
7
+
8
+ const portArg = args.indexOf("--port");
9
+ const port = portArg >= 0 ? Number(args[portArg + 1]) : 9222;
10
+ const headless = !args.includes("--no-headless");
11
+
12
+ const f = await Fortress.launch({ port, headless });
13
+ process.stderr.write(`Fortress up. CDP: ${f.cdpUrl}\n`);
14
+ console.log(f.cdpUrl);
15
+ process.on("SIGINT", async () => { await f.close(); process.exit(0); });
16
+ setInterval(() => {}, 1 << 30);
package/index.js ADDED
@@ -0,0 +1,148 @@
1
+ // tilion-fortress (Node) — install & drive the Fortress stealth Chromium engine.
2
+ // Ships the prebuilt binary only (no engine source). Detects platform, downloads the
3
+ // matching bundle from the GitHub Release, verifies SHA256, caches it, launches with CDP.
4
+ // macOS/Windows fall back to the Docker image until native binaries are published.
5
+ import { spawn, spawnSync } from "node:child_process";
6
+ import { createWriteStream, existsSync, chmodSync, mkdirSync, createReadStream } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { pipeline } from "node:stream/promises";
10
+ import { createHash } from "node:crypto";
11
+
12
+ export const VERSION = "151.0.7908.0";
13
+ const REPO = "tiliondev/fortress";
14
+ const TAG = `v${VERSION}`;
15
+ const DOCKER_IMAGE = "tilion/fortress:latest";
16
+ const CACHE = process.env.FORTRESS_BROWSERS_PATH || join(homedir(), ".cache", "tilion-fortress");
17
+ const HOST = process.env.FORTRESS_DOWNLOAD_HOST || `https://github.com/${REPO}/releases/download/${TAG}`;
18
+
19
+ // platform key -> { asset, kind, launcher }
20
+ const ASSETS = {
21
+ "linux-x64": { asset: "tilion-fortress-linux-x64.tar.gz", kind: "tar", launcher: "tilion-fortress/tilion" },
22
+ "win-x64": { asset: "tilion-fortress-win-x64.zip", kind: "zip", launcher: "tilion-fortress/tilion.cmd" },
23
+ "mac-arm64": { asset: "tilion-fortress-mac-arm64.tar.gz", kind: "tar", launcher: "tilion-fortress/tilion" },
24
+ "mac-x64": { asset: "tilion-fortress-mac-x64.tar.gz", kind: "tar", launcher: "tilion-fortress/tilion" },
25
+ };
26
+
27
+ export function resolvePlatform() {
28
+ const { platform, arch } = process;
29
+ if (platform === "linux" && arch === "x64") return "linux-x64";
30
+ if (platform === "win32" && arch === "x64") return "win-x64";
31
+ if (platform === "darwin") return arch === "arm64" ? "mac-arm64" : "mac-x64";
32
+ return null;
33
+ }
34
+
35
+ function personaArgs(persona) {
36
+ if (!persona) return [];
37
+ const map = { platform: "--uxr-platform", timezone: "--uxr-timezone", languages: "--uxr-languages",
38
+ webglRenderer: "--uxr-webgl-renderer", webglVendor: "--uxr-webgl-vendor",
39
+ hwConcurrency: "--uxr-hw-concurrency", deviceMemory: "--uxr-device-memory",
40
+ screenWidth: "--uxr-screen-width", screenHeight: "--uxr-screen-height", canvasSeed: "--uxr-canvas-seed" };
41
+ return Object.entries(persona).map(([k, v]) => `${map[k] || `--uxr-${k}`}=${v}`);
42
+ }
43
+
44
+ async function sha256(path) {
45
+ const h = createHash("sha256");
46
+ await pipeline(createReadStream(path), h);
47
+ return h.digest("hex");
48
+ }
49
+
50
+ async function expectedSha(asset) {
51
+ try {
52
+ const r = await fetch(`${HOST}/SHA256SUMS`);
53
+ if (!r.ok) return null;
54
+ for (const line of (await r.text()).split("\n")) {
55
+ const p = line.trim().split(/\s+/);
56
+ if (p.length === 2 && p[1].replace(/^\*/, "") === asset) return p[0].toLowerCase();
57
+ }
58
+ } catch { /* none */ }
59
+ return null;
60
+ }
61
+
62
+ async function ensureNative(plat) {
63
+ const { asset, kind, launcher } = ASSETS[plat];
64
+ const root = join(CACHE, VERSION, plat);
65
+ const launcherPath = join(root, launcher);
66
+ if (existsSync(launcherPath)) return launcherPath;
67
+ mkdirSync(root, { recursive: true });
68
+ const archive = join(root, asset);
69
+ process.stderr.write(`[tilion-fortress] downloading ${HOST}/${asset} ...\n`);
70
+ const res = await fetch(`${HOST}/${asset}`);
71
+ if (!res.ok) throw new Error(`download failed: ${res.status}`);
72
+ await pipeline(res.body, createWriteStream(archive));
73
+
74
+ const exp = await expectedSha(asset);
75
+ if (exp) {
76
+ const act = await sha256(archive);
77
+ if (act !== exp) throw new Error(`SHA256 mismatch for ${asset}: expected ${exp}, got ${act}`);
78
+ process.stderr.write("[tilion-fortress] SHA256 verified\n");
79
+ } else {
80
+ process.stderr.write("[tilion-fortress] WARNING: no SHA256SUMS published; skipping verification\n");
81
+ }
82
+
83
+ if (kind === "tar") {
84
+ if (spawnSync("tar", ["xzf", archive, "-C", root], { stdio: "inherit" }).status !== 0)
85
+ throw new Error("tar extraction failed");
86
+ } else { // zip (Windows): use PowerShell Expand-Archive
87
+ if (spawnSync("powershell", ["-NoProfile", "-Command",
88
+ `Expand-Archive -Force -LiteralPath '${archive}' -DestinationPath '${root}'`],
89
+ { stdio: "inherit" }).status !== 0) throw new Error("zip extraction failed");
90
+ }
91
+ if (!launcher.endsWith(".cmd") && existsSync(launcherPath)) chmodSync(launcherPath, 0o755);
92
+ if (!existsSync(launcherPath)) throw new Error(`launcher missing after extract: ${launcherPath}`);
93
+ return launcherPath;
94
+ }
95
+
96
+ async function assetExists(plat) {
97
+ try { return (await fetch(`${HOST}/${ASSETS[plat].asset}`, { method: "HEAD" })).ok; }
98
+ catch { return false; }
99
+ }
100
+
101
+ async function waitCdp(port, timeoutMs = 40000) {
102
+ const deadline = Date.now() + timeoutMs;
103
+ while (Date.now() < deadline) {
104
+ try { const r = await fetch(`http://127.0.0.1:${port}/json/version`); if (r.ok) return (await r.json()).webSocketDebuggerUrl; }
105
+ catch {}
106
+ await new Promise((r) => setTimeout(r, 500));
107
+ }
108
+ throw new Error("Fortress CDP endpoint did not come up");
109
+ }
110
+
111
+ export class Fortress {
112
+ constructor({ port = 9222, persona = null, extraArgs = [], headless = true } = {}) {
113
+ Object.assign(this, { port, persona, extraArgs, headless, proc: null, dockerName: null, cdpUrl: null });
114
+ }
115
+ static async launch(opts) { return new Fortress(opts).start(); }
116
+
117
+ async start() {
118
+ const plat = resolvePlatform();
119
+ const native = plat && (plat === "linux-x64" || await assetExists(plat));
120
+ if (native) await this._startNative(plat); else this._startDocker();
121
+ this.cdpUrl = await waitCdp(this.port);
122
+ return this;
123
+ }
124
+
125
+ async _startNative(plat) {
126
+ const launcher = await ensureNative(plat);
127
+ const args = [];
128
+ if (this.headless) args.push("--headless=new", "--no-sandbox");
129
+ args.push(`--remote-debugging-port=${this.port}`, `--user-data-dir=${join(CACHE, "profile")}`,
130
+ ...personaArgs(this.persona), ...this.extraArgs);
131
+ this.proc = spawn(launcher, args, { stdio: "ignore", shell: launcher.endsWith(".cmd") });
132
+ }
133
+
134
+ _startDocker() {
135
+ if (spawnSync("docker", ["--version"]).status !== 0)
136
+ throw new Error("No native binary for this platform yet and Docker not installed. Install Docker Desktop or use Linux x64.");
137
+ this.dockerName = `tilion-fortress-${process.pid}-${this.port}`;
138
+ const args = ["run", "-d", "--rm", "--name", this.dockerName, "-p", `${this.port}:9222`, DOCKER_IMAGE,
139
+ ...personaArgs(this.persona), ...this.extraArgs];
140
+ if (spawnSync("docker", args, { stdio: "ignore" }).status !== 0) throw new Error("docker run failed");
141
+ }
142
+
143
+ async close() {
144
+ if (this.proc) { this.proc.kill(); this.proc = null; }
145
+ if (this.dockerName) { spawnSync("docker", ["rm", "-f", this.dockerName], { stdio: "ignore" }); this.dockerName = null; }
146
+ }
147
+ }
148
+ export default Fortress;
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "tilion-fortress",
3
+ "version": "151.0.7908",
4
+ "description": "Install and drive the Fortress stealth Chromium engine. Prebuilt binary, no source.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": { "tilion-fortress": "cli.js" },
8
+ "files": ["index.js", "cli.js", "README.md"],
9
+ "engines": { "node": ">=18" },
10
+ "keywords": ["chromium", "stealth", "browser", "automation", "anti-bot", "fortress", "tilion"],
11
+ "author": "arham766",
12
+ "license": "BSD-3-Clause",
13
+ "repository": { "type": "git", "url": "https://github.com/tiliondev/fortress.git" },
14
+ "homepage": "https://github.com/tiliondev/fortress"
15
+ }