Skip to content

Commit f3f932d

Browse files
authored
Merge pull request #8 from tiliondev/chore/repo-hardening
chore: repo-hardening (gauntlet Windows fix, gitattributes, templates, Makefile, security/contributing)
2 parents 50c2793 + 988dd13 commit f3f932d

17 files changed

Lines changed: 404 additions & 111 deletions

.editorconfig

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# https://editorconfig.org — consistent formatting across editors.
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
end_of_line = lf
7+
insert_final_newline = true
8+
trim_trailing_whitespace = true
9+
indent_style = space
10+
indent_size = 2
11+
12+
[*.py]
13+
indent_size = 4
14+
15+
[*.{sh,gn}]
16+
indent_size = 2
17+
18+
[Makefile]
19+
indent_style = tab
20+
21+
# Patches are verbatim diffs — do not let an editor rewrite their whitespace.
22+
[*.patch]
23+
trim_trailing_whitespace = false
24+
insert_final_newline = false
25+
26+
[*.{cmd,ps1}]
27+
end_of_line = crlf

.gitattributes

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Normalize line endings so a Windows checkout can't corrupt the patch set.
2+
# `git apply` on patches/*.patch is whitespace-sensitive; CRLF creep silently breaks it.
3+
4+
# Default: normalize text to LF in the repo, let git pick the working-tree ending.
5+
* text=auto
6+
7+
# These MUST stay LF everywhere (scripts run under sh; patches are applied verbatim).
8+
*.patch text eol=lf
9+
*.sh text eol=lf
10+
*.py text eol=lf
11+
*.js text eol=lf
12+
*.mjs text eol=lf
13+
*.yml text eol=lf
14+
*.yaml text eol=lf
15+
*.json text eol=lf
16+
*.gn text eol=lf
17+
*.conf text eol=lf
18+
tilion text eol=lf
19+
series text eol=lf
20+
Makefile text eol=lf
21+
22+
# Windows-only helpers keep CRLF.
23+
*.cmd text eol=crlf
24+
*.ps1 text eol=crlf
25+
26+
# Binary assets — never touch.
27+
*.png binary
28+
*.jpg binary
29+
*.jpeg binary
30+
*.gif binary
31+
*.ico binary
32+
*.ttf binary
33+
*.otf binary
34+
*.woff binary
35+
*.woff2 binary
36+
*.dat binary
37+
*.bin binary
38+
*.tar.gz binary
39+
*.zip binary
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Bug report
2+
description: Something in Fortress or the SDKs is broken (not a detection vector)
3+
title: "bug: "
4+
labels: ["bug"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
For a page that *fingerprints* Fortress, use the "Detection vector" template instead.
10+
For a security issue (crash, sandbox escape, host leak), do not file here — see SECURITY.md.
11+
- type: textarea
12+
id: what
13+
attributes:
14+
label: What happened
15+
description: What you did, what you expected, and what actually happened.
16+
validations:
17+
required: true
18+
- type: textarea
19+
id: repro
20+
attributes:
21+
label: Steps to reproduce
22+
placeholder: |
23+
1. docker run -p 9222:9222 tilion/fortress:latest
24+
2. connect_over_cdp("http://localhost:9222")
25+
3. ...
26+
validations:
27+
required: true
28+
- type: input
29+
id: version
30+
attributes:
31+
label: Version
32+
description: Docker tag, pip/npm version, or bundle + Chromium version.
33+
validations:
34+
required: true
35+
- type: input
36+
id: platform
37+
attributes:
38+
label: Platform
39+
placeholder: "macOS 14 arm64 / Ubuntu 24.04 x64 / Windows 11"
40+
validations:
41+
required: true
42+
- type: textarea
43+
id: logs
44+
attributes:
45+
label: Logs
46+
description: Any relevant output.
47+
render: shell

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Security vulnerability
4+
url: https://github.com/tiliondev/fortress/security/advisories/new
5+
about: Report a crash, sandbox escape, or host leak privately (see SECURITY.md) — not a public issue.
6+
- name: Question or usage help
7+
url: https://github.com/tiliondev/fortress/discussions
8+
about: Ask how to set Fortress up or drive it — see AGENTS.md first.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Detection vector
2+
description: A page or script that tells Fortress apart from real Chrome
3+
title: "detection: "
4+
labels: ["detection"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
A reproducible detector is the single most valuable thing you can send. Before filing,
10+
please sanity-check it is a **fingerprint** issue and not an **IP** one: most "it got
11+
blocked" reports are the datacenter IP being flagged before any page script runs. Re-run
12+
through a residential/mobile proxy first — if it clears, the fingerprint was fine.
13+
- type: textarea
14+
id: repro
15+
attributes:
16+
label: Reproduction
17+
description: A URL, or a self-contained HTML/JS snippet, that separates Fortress from real Chrome.
18+
placeholder: |
19+
https://example-detector.test
20+
— or —
21+
<script>document.title = navigator.webdriver</script>
22+
validations:
23+
required: true
24+
- type: textarea
25+
id: observed
26+
attributes:
27+
label: Observed vs expected
28+
description: What Fortress shows, and what stock Chrome shows (values or screenshots).
29+
validations:
30+
required: true
31+
- type: input
32+
id: launch
33+
attributes:
34+
label: How you launched Fortress
35+
description: Docker tag, pip/npm version, or bundle + the exact flags.
36+
placeholder: "docker run tilion/fortress:latest / pip tilion-fortress 151.0.7908.0.post2"
37+
validations:
38+
required: true
39+
- type: input
40+
id: env
41+
attributes:
42+
label: Host OS and egress
43+
placeholder: "Ubuntu 24.04, residential proxy"
44+
- type: checkboxes
45+
id: confirm
46+
attributes:
47+
label: Confirm
48+
options:
49+
- label: I verified this reproduces on a non-datacenter IP (or IP is not the factor here).
50+
required: true

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!-- Thanks for contributing to Fortress. Keep PRs focused; see CONTRIBUTING.md. -->
2+
3+
## What this changes
4+
5+
<!-- One or two sentences. Link the issue it closes, e.g. "Closes #123". -->
6+
7+
## Type
8+
9+
- [ ] Fingerprint patch (touches `patches/`)
10+
- [ ] SDK / tooling / packaging
11+
- [ ] Docs
12+
- [ ] CI / infra
13+
14+
## Checklist
15+
16+
- [ ] `python tools/check_patches.py` passes
17+
- [ ] `python -m pytest sdk/python/tests -q` passes (if SDK touched)
18+
- [ ] If this touches `patches/`: it is **one file per patch**, added to `patches/series`, and uses
19+
only the `uxr-` switch prefix (no brand strings baked into the binary)
20+
- [ ] Any limitation or partial fix is written down (no oversold "undetectable" claims)
21+
- [ ] For a surface change: before/after value on Fortress vs stock Chrome is in the description

.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,21 @@ dist/
1818
.vscode/
1919
.idea/
2020
*.swp
21+
22+
# Python packaging build artifacts (generated by build/setuptools)
23+
*.egg-info/
24+
*.egg
25+
26+
# test / lint caches
27+
.pytest_cache/
28+
.mypy_cache/
29+
.ruff_cache/
30+
.tox/
31+
32+
# local virtualenvs
33+
.venv/
34+
venv/
35+
36+
# OS cruft
37+
.DS_Store
38+
Thumbs.db

.pre-commit-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# https://pre-commit.com — run the same cheap gates as CI before every commit.
2+
# pip install pre-commit && pre-commit install
3+
minimum_pre_commit_version: "3.0.0"
4+
5+
repos:
6+
- repo: https://github.com/pre-commit/pre-commit-hooks
7+
rev: v4.6.0
8+
hooks:
9+
- id: trailing-whitespace
10+
exclude: ^patches/ # patches are verbatim diffs
11+
- id: end-of-file-fixer
12+
exclude: ^patches/
13+
- id: check-yaml
14+
- id: check-json
15+
- id: check-merge-conflict
16+
- id: mixed-line-ending
17+
args: [--fix=lf]
18+
exclude: '\.(cmd|ps1)$'
19+
20+
- repo: local
21+
hooks:
22+
- id: check-patches
23+
name: patch-set integrity linter
24+
entry: python tools/check_patches.py
25+
language: system
26+
pass_filenames: false
27+
files: ^patches/

CONTRIBUTING.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Contributing to Fortress
2+
3+
Fortress is a stealth Chromium engine that corrects fingerprint surfaces in the browser's **C++**,
4+
then exposes raw CDP on `http://localhost:9222` as a drop-in for Playwright and Puppeteer. This
5+
guide covers how to report a detection and how to get a change merged.
6+
7+
## The single most valuable contribution
8+
9+
**A page that reliably flags Fortress.** A minimal, reproducible detector — a URL or short script
10+
that separates Fortress from real Chrome — is worth more than any feature. Open an issue with the
11+
**Detection vector** template.
12+
13+
Before filing, sanity-check it is a **fingerprint** issue and not an **IP** one: roughly 90% of
14+
"it got blocked" reports are the datacenter IP getting flagged before any page script runs. Re-run
15+
through a residential or mobile proxy first — if it clears, the fingerprint was fine. See rule 4 in
16+
[AGENTS.md](AGENTS.md).
17+
18+
## Two house rules
19+
20+
1. **Every claim ships with a way to reproduce it.** A patch that changes a surface comes with the
21+
command or test page that shows the before/after.
22+
2. **Every limitation is written down.** If a patch is partial, say so in the patch header and the
23+
docs. The word *undetectable* stays out of this project — we correct specific, named surfaces.
24+
25+
## How the patch set is organized
26+
27+
Fortress is a set of source patches applied to a pinned Chromium checkout (`CHROMIUM_VERSION`), not
28+
a runtime library.
29+
30+
- **`patches/`** — one patch per file, numbered, **single-surface**. `0002`/`0003` are the
31+
`base::UxrConfig` singleton every override reads from; the rest each touch one place.
32+
- **`patches/series`** — the apply order. **A patch not listed here is silently skipped** by
33+
`build/apply-patches.sh`, so always add your patch to `series`.
34+
- **`build/apply-patches.sh`** applies the series onto a Chromium `src/`.
35+
- **`tools/gauntlet.py`** — the live detection harness (CreepJS / Sannysoft / BrowserScan).
36+
37+
Full build instructions: [docs/BUILD_NATIVE.md](docs/BUILD_NATIVE.md). Expect a multi-hour first
38+
compile; incremental rebuilds after a one-line patch are minutes.
39+
40+
### The de-branded switch prefix — do not rename it
41+
42+
Runtime overrides are exposed as `--uxr-*` flags read through `base::UxrConfig`. That prefix is
43+
intentional and **must stay `uxr`** — a neutral token so the binary carries no product string a
44+
detector could match. A new surface means a new `--uxr-<surface>` flag; never a `--fortress-*` /
45+
`--tilion-*` flag, and never a brand string literal baked into the binary.
46+
47+
## Before you open a PR — run the checks
48+
49+
CI runs these on every PR; run them locally first (`make check`):
50+
51+
```bash
52+
python tools/check_patches.py # patch-set integrity (series, numbering, single-surface, uxr-only)
53+
python -m pytest sdk/python/tests -q
54+
```
55+
56+
Optionally install the git hooks so they run automatically:
57+
58+
```bash
59+
pip install pre-commit && pre-commit install
60+
```
61+
62+
## Submitting a change
63+
64+
1. **Open an issue first** for anything beyond a typo, so we can agree on the surface and approach.
65+
2. **Branch** from `main`, focused on one surface / one fix.
66+
3. **One patch per file, single-surface**, and add it to `patches/series`.
67+
4. **Verify** with `tools/gauntlet.py`; paste the before/after into the PR.
68+
5. **Rebase, don't merge**`git fetch && git rebase origin/main` before pushing. The patch set is
69+
rebased monthly onto new Chromium; a linear history keeps that sane.
70+
71+
Docs, examples, the gauntlet, packaging, and the SDKs do **not** require a Chromium build — a great
72+
place to start.
73+
74+
## Security
75+
76+
A page that *fingerprints* Fortress is not a security issue — file it in the open. A crash, sandbox
77+
escape, or host leak **is** — report it privately per [SECURITY.md](SECURITY.md).
78+
79+
## Licensing
80+
81+
Fortress is BSD-3-Clause (a Chromium derivative — see [LICENSE](LICENSE) and [NOTICE](NOTICE)). By
82+
contributing, you agree your contribution is licensed under the same terms.

Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Fortress developer tasks. These wrap the scripts that already live in the repo;
2+
# nothing here compiles Chromium (see docs/BUILD_NATIVE.md for that).
3+
.DEFAULT_GOAL := help
4+
PYTHON ?= python3
5+
BUNDLE ?= dist/tilion-fortress
6+
7+
.PHONY: help lint test check gauntlet apply bundle clean
8+
9+
help: ## Show this help
10+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
11+
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
12+
13+
lint: ## Run the patch-set integrity linter
14+
$(PYTHON) tools/check_patches.py
15+
16+
test: ## Run the Python SDK unit tests
17+
$(PYTHON) -m pytest sdk/python/tests -q
18+
19+
check: lint test ## Lint + test (what CI gates on)
20+
21+
gauntlet: ## Run the live detection gauntlet against a bundle (BUNDLE=/path/to/tilion-fortress)
22+
$(PYTHON) tools/gauntlet.py --bundle $(BUNDLE)
23+
24+
apply: ## Apply the patch series onto a Chromium checkout (SRC=/path/to/chromium/src)
25+
@test -n "$(SRC)" || { echo "usage: make apply SRC=/path/to/chromium/src"; exit 2; }
26+
build/apply-patches.sh $(SRC)
27+
28+
bundle: ## Assemble the portable bundle (SRC=<out/Fortress> FONTS=<fonts dir> DEST=<dest>)
29+
@test -n "$(SRC)" && test -n "$(DEST)" || { echo "usage: make bundle SRC=<out dir> FONTS=fonts DEST=dist"; exit 2; }
30+
packaging/build-bundle.sh $(SRC) $(FONTS) $(DEST)
31+
32+
clean: ## Remove local build/test caches
33+
rm -rf .pytest_cache **/__pycache__ sdk/python/*.egg-info dist/*.tar.gz dist/SHA256SUMS

0 commit comments

Comments
 (0)