Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://editorconfig.org — consistent formatting across editors.
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[*.py]
indent_size = 4

[*.{sh,gn}]
indent_size = 2

[Makefile]
indent_style = tab

# Patches are verbatim diffs — do not let an editor rewrite their whitespace.
[*.patch]
trim_trailing_whitespace = false
insert_final_newline = false

[*.{cmd,ps1}]
end_of_line = crlf
39 changes: 39 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Normalize line endings so a Windows checkout can't corrupt the patch set.
# `git apply` on patches/*.patch is whitespace-sensitive; CRLF creep silently breaks it.

# Default: normalize text to LF in the repo, let git pick the working-tree ending.
* text=auto

# These MUST stay LF everywhere (scripts run under sh; patches are applied verbatim).
*.patch text eol=lf
*.sh text eol=lf
*.py text eol=lf
*.js text eol=lf
*.mjs text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.json text eol=lf
*.gn text eol=lf
*.conf text eol=lf
tilion text eol=lf
series text eol=lf
Makefile text eol=lf

# Windows-only helpers keep CRLF.
*.cmd text eol=crlf
*.ps1 text eol=crlf

# Binary assets — never touch.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dat binary
*.bin binary
*.tar.gz binary
*.zip binary
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Bug report
description: Something in Fortress or the SDKs is broken (not a detection vector)
title: "bug: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
For a page that *fingerprints* Fortress, use the "Detection vector" template instead.
For a security issue (crash, sandbox escape, host leak), do not file here — see SECURITY.md.
- type: textarea
id: what
attributes:
label: What happened
description: What you did, what you expected, and what actually happened.
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
placeholder: |
1. docker run -p 9222:9222 tilion/fortress:latest
2. connect_over_cdp("http://localhost:9222")
3. ...
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Docker tag, pip/npm version, or bundle + Chromium version.
validations:
required: true
- type: input
id: platform
attributes:
label: Platform
placeholder: "macOS 14 arm64 / Ubuntu 24.04 x64 / Windows 11"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: Any relevant output.
render: shell
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/tiliondev/fortress/security/advisories/new
about: Report a crash, sandbox escape, or host leak privately (see SECURITY.md) — not a public issue.
- name: Question or usage help
url: https://github.com/tiliondev/fortress/discussions
about: Ask how to set Fortress up or drive it — see AGENTS.md first.
50 changes: 50 additions & 0 deletions .github/ISSUE_TEMPLATE/detection_vector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Detection vector
description: A page or script that tells Fortress apart from real Chrome
title: "detection: "
labels: ["detection"]
body:
- type: markdown
attributes:
value: |
A reproducible detector is the single most valuable thing you can send. Before filing,
please sanity-check it is a **fingerprint** issue and not an **IP** one: most "it got
blocked" reports are the datacenter IP being flagged before any page script runs. Re-run
through a residential/mobile proxy first — if it clears, the fingerprint was fine.
- type: textarea
id: repro
attributes:
label: Reproduction
description: A URL, or a self-contained HTML/JS snippet, that separates Fortress from real Chrome.
placeholder: |
https://example-detector.test
— or —
<script>document.title = navigator.webdriver</script>
validations:
required: true
- type: textarea
id: observed
attributes:
label: Observed vs expected
description: What Fortress shows, and what stock Chrome shows (values or screenshots).
validations:
required: true
- type: input
id: launch
attributes:
label: How you launched Fortress
description: Docker tag, pip/npm version, or bundle + the exact flags.
placeholder: "docker run tilion/fortress:latest / pip tilion-fortress 151.0.7908.0.post2"
validations:
required: true
- type: input
id: env
attributes:
label: Host OS and egress
placeholder: "Ubuntu 24.04, residential proxy"
- type: checkboxes
id: confirm
attributes:
label: Confirm
options:
- label: I verified this reproduces on a non-datacenter IP (or IP is not the factor here).
required: true
21 changes: 21 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Thanks for contributing to Fortress. Keep PRs focused; see CONTRIBUTING.md. -->

## What this changes

<!-- One or two sentences. Link the issue it closes, e.g. "Closes #123". -->

## Type

- [ ] Fingerprint patch (touches `patches/`)
- [ ] SDK / tooling / packaging
- [ ] Docs
- [ ] CI / infra

## Checklist

- [ ] `python tools/check_patches.py` passes
- [ ] `python -m pytest sdk/python/tests -q` passes (if SDK touched)
- [ ] If this touches `patches/`: it is **one file per patch**, added to `patches/series`, and uses
only the `uxr-` switch prefix (no brand strings baked into the binary)
- [ ] Any limitation or partial fix is written down (no oversold "undetectable" claims)
- [ ] For a surface change: before/after value on Fortress vs stock Chrome is in the description
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,21 @@ dist/
.vscode/
.idea/
*.swp

# Python packaging build artifacts (generated by build/setuptools)
*.egg-info/
*.egg

# test / lint caches
.pytest_cache/
.mypy_cache/
.ruff_cache/
.tox/

# local virtualenvs
.venv/
venv/

# OS cruft
.DS_Store
Thumbs.db
27 changes: 27 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://pre-commit.com — run the same cheap gates as CI before every commit.
# pip install pre-commit && pre-commit install
minimum_pre_commit_version: "3.0.0"

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
exclude: ^patches/ # patches are verbatim diffs
- id: end-of-file-fixer
exclude: ^patches/
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: mixed-line-ending
args: [--fix=lf]
exclude: '\.(cmd|ps1)$'

- repo: local
hooks:
- id: check-patches
name: patch-set integrity linter
entry: python tools/check_patches.py
language: system
pass_filenames: false
files: ^patches/
82 changes: 82 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Contributing to Fortress

Fortress is a stealth Chromium engine that corrects fingerprint surfaces in the browser's **C++**,
then exposes raw CDP on `http://localhost:9222` as a drop-in for Playwright and Puppeteer. This
guide covers how to report a detection and how to get a change merged.

## The single most valuable contribution

**A page that reliably flags Fortress.** A minimal, reproducible detector — a URL or short script
that separates Fortress from real Chrome — is worth more than any feature. Open an issue with the
**Detection vector** template.

Before filing, sanity-check it is a **fingerprint** issue and not an **IP** one: roughly 90% of
"it got blocked" reports are the datacenter IP getting flagged before any page script runs. Re-run
through a residential or mobile proxy first — if it clears, the fingerprint was fine. See rule 4 in
[AGENTS.md](AGENTS.md).

## Two house rules

1. **Every claim ships with a way to reproduce it.** A patch that changes a surface comes with the
command or test page that shows the before/after.
2. **Every limitation is written down.** If a patch is partial, say so in the patch header and the
docs. The word *undetectable* stays out of this project — we correct specific, named surfaces.

## How the patch set is organized

Fortress is a set of source patches applied to a pinned Chromium checkout (`CHROMIUM_VERSION`), not
a runtime library.

- **`patches/`** — one patch per file, numbered, **single-surface**. `0002`/`0003` are the
`base::UxrConfig` singleton every override reads from; the rest each touch one place.
- **`patches/series`** — the apply order. **A patch not listed here is silently skipped** by
`build/apply-patches.sh`, so always add your patch to `series`.
- **`build/apply-patches.sh`** applies the series onto a Chromium `src/`.
- **`tools/gauntlet.py`** — the live detection harness (CreepJS / Sannysoft / BrowserScan).

Full build instructions: [docs/BUILD_NATIVE.md](docs/BUILD_NATIVE.md). Expect a multi-hour first
compile; incremental rebuilds after a one-line patch are minutes.

### The de-branded switch prefix — do not rename it

Runtime overrides are exposed as `--uxr-*` flags read through `base::UxrConfig`. That prefix is
intentional and **must stay `uxr`** — a neutral token so the binary carries no product string a
detector could match. A new surface means a new `--uxr-<surface>` flag; never a `--fortress-*` /
`--tilion-*` flag, and never a brand string literal baked into the binary.

## Before you open a PR — run the checks

CI runs these on every PR; run them locally first (`make check`):

```bash
python tools/check_patches.py # patch-set integrity (series, numbering, single-surface, uxr-only)
python -m pytest sdk/python/tests -q
```

Optionally install the git hooks so they run automatically:

```bash
pip install pre-commit && pre-commit install
```

## Submitting a change

1. **Open an issue first** for anything beyond a typo, so we can agree on the surface and approach.
2. **Branch** from `main`, focused on one surface / one fix.
3. **One patch per file, single-surface**, and add it to `patches/series`.
4. **Verify** with `tools/gauntlet.py`; paste the before/after into the PR.
5. **Rebase, don't merge** — `git fetch && git rebase origin/main` before pushing. The patch set is
rebased monthly onto new Chromium; a linear history keeps that sane.

Docs, examples, the gauntlet, packaging, and the SDKs do **not** require a Chromium build — a great
place to start.

## Security

A page that *fingerprints* Fortress is not a security issue — file it in the open. A crash, sandbox
escape, or host leak **is** — report it privately per [SECURITY.md](SECURITY.md).

## Licensing

Fortress is BSD-3-Clause (a Chromium derivative — see [LICENSE](LICENSE) and [NOTICE](NOTICE)). By
contributing, you agree your contribution is licensed under the same terms.
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Fortress developer tasks. These wrap the scripts that already live in the repo;
# nothing here compiles Chromium (see docs/BUILD_NATIVE.md for that).
.DEFAULT_GOAL := help
PYTHON ?= python3
BUNDLE ?= dist/tilion-fortress

.PHONY: help lint test check gauntlet apply bundle clean

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'

lint: ## Run the patch-set integrity linter
$(PYTHON) tools/check_patches.py

test: ## Run the Python SDK unit tests
$(PYTHON) -m pytest sdk/python/tests -q

check: lint test ## Lint + test (what CI gates on)

gauntlet: ## Run the live detection gauntlet against a bundle (BUNDLE=/path/to/tilion-fortress)
$(PYTHON) tools/gauntlet.py --bundle $(BUNDLE)

apply: ## Apply the patch series onto a Chromium checkout (SRC=/path/to/chromium/src)
@test -n "$(SRC)" || { echo "usage: make apply SRC=/path/to/chromium/src"; exit 2; }
build/apply-patches.sh $(SRC)

bundle: ## Assemble the portable bundle (SRC=<out/Fortress> FONTS=<fonts dir> DEST=<dest>)
@test -n "$(SRC)" && test -n "$(DEST)" || { echo "usage: make bundle SRC=<out dir> FONTS=fonts DEST=dist"; exit 2; }
packaging/build-bundle.sh $(SRC) $(FONTS) $(DEST)

clean: ## Remove local build/test caches
rm -rf .pytest_cache **/__pycache__ sdk/python/*.egg-info dist/*.tar.gz dist/SHA256SUMS
34 changes: 34 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Security Policy

## What counts as what

Fortress is a stealth Chromium engine, so please route two very different things to two
different places:

- **A detection vector** — a page or script that fingerprints Fortress and tells it apart from
real Chrome — is **not** a security vulnerability. It is exactly what the project wants, and it
belongs in a **public issue** (use the "Detection vector" template). The more reproducible, the
better.
- **A vulnerability in Fortress itself** — a way to crash the binary, escape the sandbox, leak the
host (files, environment, real IP outside the configured proxy), or compromise a machine running
it — is a **security issue**. Report it **privately** (below), not in a public issue.

## Reporting a vulnerability

Please use **GitHub's private vulnerability reporting**: the **Security** tab of this repository →
**Report a vulnerability**. That keeps the report private to the maintainers while it is triaged.

Include, as far as you can:

- affected version (Docker tag, `pip`/`npm` version, or bundle + Chromium version from `CHROMIUM_VERSION`),
- the platform, and
- a minimal reproduction and the impact.

We aim to acknowledge a report within a few days and to keep you updated as we work on a fix.
Please give us a reasonable window to release a fix before disclosing publicly.

## Supported versions

Fortress tracks stable Chromium and is released from the tip of `main`. Security fixes land on the
**latest** release; older tagged releases are not maintained. Always verify a download against the
release `SHA256SUMS` (the `pip`/`npm` SDKs do this automatically).
Loading
Loading