Commit graph

151 commits

Author SHA1 Message Date
feder-cr
eeddb8fcde docs: promote the proxy section to a top-level heading 2026-06-30 03:56:03 +02:00
feder-cr
7e06d60f04 docs: truthful proxy framing (public/known IPs, not datacenter) 2026-06-30 01:47:09 +02:00
feder-cr
92b374a497 docs: bulleted How it works, bridge to solution, drop the suites claim 2026-06-30 01:38:52 +02:00
feder-cr
fa869c52b8 docs: merge the C++ paragraph into the intro (drop the subheading) 2026-06-30 01:30:10 +02:00
feder-cr
7a42984158 fix(deps): cap playwright <1.61 and pin it in the e2e clean venv
Playwright 1.61.0 (2026-06-29) ships a Browser.setDefaultViewport Juggler
call the published firefox-13 binary does not implement, so an unpinned
pip install (the wrapper declared playwright>=1.40) breaks new_context on
fresh installs. Cap the dependency to <1.61 and force the blessed pin in
the e2e clean venv so CI tests the version users actually run.
2026-06-30 09:00:00 +00:00
feder-cr
95b1e47e0c docs: align Proxies usage note to 90/10 for consistency 2026-06-29 18:07:13 +02:00
feder-cr
62007c21d8 docs: rewrite How it works (browser undetectable, proxy is the remaining variable) 2026-06-29 18:06:46 +02:00
feder-cr
6cca6b3a69 docs: sharpen the proxy framing (99% already flagged as proxies) 2026-06-29 17:48:35 +02:00
feder-cr
3046ca17d3 docs: phrase sx.org as a recommendation, not a partnership 2026-06-29 17:46:21 +02:00
feder-cr
2aa14d9b5b docs: drop the competitor-naming intro line in How it compares 2026-06-29 17:44:21 +02:00
feder-cr
0b5edf4e35 docs: note proxy partner in the Proxies usage section 2026-06-29 17:37:50 +02:00
feder-cr
0e6b0dddfe docs: add proxy partner note to How it works 2026-06-29 17:32:29 +02:00
feder-cr
2184f6f3c2 docs: restore C++ paragraph content, remove em-dashes 2026-06-25 04:32:32 +02:00
feder-cr
d35ac458a7 docs: remove LLM phrasing throughout README 2026-06-25 04:31:47 +02:00
feder-cr
567d921d5f docs: merge and trim C++ patch paragraphs 2026-06-25 04:30:01 +02:00
feder-cr
0b54484b63 docs: remove auth implementation detail from README 2026-06-25 04:28:44 +02:00
feder-cr
be940b0084 docs: remove preferences line from README 2026-06-25 04:27:32 +02:00
feder-cr
29df4e873c docs: shorten Chromium point in README 2026-06-25 04:26:40 +02:00
feder-cr
9b9e63972c docs: add educational disclaimer to README 2026-06-25 04:25:03 +02:00
feder-cr
168179eb03 docs: remove LLM phrasing from README 2026-06-25 04:23:59 +02:00
feder-cr
02b5f397ad docs: rewrite SECURITY.md, add disclaimer, remove LLM language 2026-06-25 04:22:39 +02:00
feder-cr
82eb22634e chore: remove LLM-style phrasing from issue templates 2026-06-25 04:16:47 +02:00
feder-cr
929c30219f chore: remove security vulnerability contact link from issue config 2026-06-25 04:15:08 +02:00
feder-cr
01d56601ae chore: remove feature request issue template 2026-06-25 04:14:05 +02:00
feder-cr
b00bd74a9e chore: remove firefox source bug link from issue config 2026-06-25 04:08:24 +02:00
feder-cr
64641db82e chore: update stealth detection issue template
- add Firefox revision field
- replace verbose verdict field with reproduction script
- rename snippet label to Reproduction script
2026-06-25 04:07:07 +02:00
feder-cr
74b674516b fix(fonts): core pool uses the real Win11 family "franklin gothic medium"
The pool had the bare "franklin gothic", which isn't a real Win11 family and
wouldn't match the bundled franklingothic_x.ttf, so the always-present standard
fonts test failed and the family could drop off the whitelist. Use the real
name (the bundle reconciliation already maps it).
2026-06-24 09:37:11 +02:00
feder-cr
2bc52f4a61 Pin firefox-13: geo-aware locale/Intl + Windows font bundle + audio gate
firefox-13 ships the bundled Windows fonts, makes navigator.languages /
Accept-Language / the Intl locale all follow the proxy country, and gates the
audio fingerprint noise off. The prefs.py change that emits the new font/locale
prefs needs this binary, so the bump and that change land together.
2026-06-24 09:33:45 +02:00
feder-cr
3c0efa2d4f Geo-aware locale, audio noise off, font prefs for the bundled-font binary
locale defaults to "auto" and resolves from the proxy egress country the same
way timezone does - it reuses the egress IP, maps the country to a BCP-47 locale
with the offline mmdb, and falls back to en-US. prefs emit juggler.locale.override
(the full Accept-Language list) so the binary keeps navigator.languages and the
Intl default locale in sync.

The audio fingerprint noise is off in the baseline. Font prefs match the new
binary: the sampled whitelist drives font.system.whitelist, system-ui is Segoe
UI, the bundled fonts are activated, and the CSS generics are pinned to Windows
defaults so they resolve on a non-Windows host too.
2026-06-23 17:51:58 +02:00
feder-cr
8f4b20a19d Pin firefox-12: cross-OS render parity + always-present standard fonts
- BINARY_VERSION -> firefox-12 (self-calibrating font widths, per-family canvas
  distinctness, render-noise that preserves solid reference renders).
- font_pool: the standard Windows fonts (Calibri, Franklin Gothic, Gadugi,
  Javanese Text, Myanmar Text) move from the per-profile optional set to core,
  so they are always present and the detected font set matches a real Windows
  install on every host. Defensive dedup in derive_font_prefs.
- GPU persona applied on every platform (Linux/macOS present a coherent Windows
  GPU + WebGL params); pool re-rooted on a real-device GPU mix; render seeds
  recalibrated.
- prefs: emit absolute per-family font widths that the binary self-calibrates.
- geoip: always pull the latest mmdb via the releases/latest permalink, checked
  each launch, offline-safe (no pinned tag that can 404).
- tests: per-font canvas distinctness, solid-readback purity under render-noise,
  always-present standard-font invariant, no duplicate families.
2026-06-19 03:19:30 +02:00
feder-cr
6dcdc42c05 Drop the dead zoom.stealth.normalize_date_now baseline pref
The Date.now self-assignment was a stopgap that the page-realm fix (the
automation layer no longer touches the realm's built-ins before page scripts
run) made unnecessary; that binary support was never landed, so the pref is a
no-op on every shipped build. Remove it and its now-inaccurate comment.
2026-06-17 00:48:20 +02:00
feder-cr
c34bc33686 Pin the wrapper to firefox-11
Bump BINARY_VERSION firefox-10 -> firefox-11 so first-run fetches the new
patched build (keeps the automation layer out of the page realm's observable
state + synthetic-cursor input realism). Add the Date.now property
normalization to the baseline prefs, paired with the firefox-11 binary's
zoom.stealth.normalize_date_now support.
2026-06-17 00:26:58 +02:00
feder-cr
30a79cefc9 Drive gate: set the realistic newtab prefs so a human-cursor click can't lose the about:newtab race
FF150+Fission auto-loads about:newtab (TopSitesFeed) ~100ms-1s after a tab's
first navigation — a cross-process BC swap that replaces the page. The wrapper
always disables this (prefs.py); the raw-Playwright drive gate did not, so any
action that adds latency (e.g. the human-cursor motion path) let the page vanish
mid-sequence and surfaced as a phantom "waiting for locator" timeout — an
environment artifact, not a binary defect. The gate now launches with the same
newtab-suppression prefs every real run uses.

Add republish.yml: re-gate + publish an existing build run's artifacts without
rebuilding, for when all builds succeeded but a gate/test bug blocked publish.
2026-06-16 23:40:29 +02:00
feder-cr
29262a644e webgl: ship only the GPU buckets that pass tampering_ml + decouple render-noise seed
Cut the per-seed WebGL persona to the two renderer buckets that score clean on
FP Pro tampering_ml across seeds (AMD Radeon R9 200 Series and Intel Arc A750),
weighted 70/30, cross-vendor so the fleet isn't one fixed GPU. Every NVIDIA
bucket and the integrated/ancient Intel buckets are penalised, so they're out.

The canvas/WebGL render-image hash turned out to be the dominant tampering_ml
driver, not the attributes, so the render-noise seed (zoom.stealth.fpp.hw_seed)
is now decoupled from the identity seed and drawn from a calibrated clean pool.
Per-seed determinism and per-user diversity are preserved.

Also in this change:
- audio maxChannelCount is stereo-dominant per class (it reflects the output
  device, not the GPU; the old tables over-emitted 5.1/7.1 surround)
- route discrete Intel Arc desktop cards to a discrete-GPU class (not integrated)
- condition the whole sampled profile on the exposed GPU class via the sampler's
  evidence path, so cores/screen/storage stay coherent with the declared GPU
- apply per-named-font width factors on Windows/macOS so canvas measureText
  widths don't collapse to a single value

12/12 seeds clean on tampering_ml (worst 0.29), bot and anti-detect negative,
and the fingerprint stays identical across repeated runs of the same seed.
2026-06-14 11:53:33 +02:00
feder-cr
2dfa4e7bd7 fix: match stock Firefox TLS ClientHello (drop cipher 0xC009)
The Playwright/Juggler Firefox build re-enables cipher 0xC009
(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), which retail Firefox 150 does not offer.
That extra (17th) cipher shifted our JA3/JA4 off every real Firefox
(ja4 t13d1717h2 / ja3 6f7889b9 vs stock t13d1617h2 / 6447ab08) — a TLS
fingerprint that matches no real browser, which is itself a consistency tell.
Set security.ssl3.ecdhe_ecdsa_aes_128_sha=false in _BASELINE so JA3/JA4/peetprint
are byte-identical to retail FF150 (verified on tls.peet.ws). Non-breaking: stock
Firefox ships without 0xC009 and works on the whole web.
2026-06-12 17:40:48 +02:00
feder-cr
b34ecf2a21 fix: humanize pref namespace + async headless cloak
humanize: the wrapper wrote invisible_playwright.humanize[.maxTime], but the
binary's Juggler reads stealthfox.humanize (PageHandler.js gates the Bezier
mouse path on it). The old name was a dead no-op, so humanize never fired and
every mouse.move teleported the cursor — an automation tell. Renamed across
config.py, launcher.py and async_api.py; the mouse test now asserts the on/off
contrast instead of a false-green moves>=1.

headless (async): InvisiblePlaywright(headless=True) crashed on Windows/macOS.
_resolve_headless called make_virtual_display().start() unconditionally, but on
Win/macOS that returns None (the binary self-cloaks via DWMWA_CLOAK; only Linux
spawns Xvfb), so it died with AttributeError. It also never injected
cloak_prefs(), so the window wouldn't have hidden anyway. Mirror the sync
launcher: guard `if vd is not None` + inject cloak_prefs() when headless on
win32/darwin. Verified on FF150: headless=True loads, exits clean, window fully
hidden (no MainWindowHandle / no taskbar entry).
2026-06-12 17:31:31 +02:00
feder-cr
090baa6155 test: add fpscanner + CreepJS to the offline real-detector gate
Alongside BotD and FingerprintJS, the detector e2e now runs two more FOSS
client-side detectors against the binary, vendored and served from localhost so it
stays offline on CI. fpscanner: assert its engine-agnostic bot rules (webdriver/
selenium/bot-UA/platform/timezone/language) are clean. CreepJS (the Firefox-aware
one): runs fully offline via window.Fingerprint with every non-loopback request
aborted, asserting headlessRating==0 and no JS-proxy stealth tell. The Chrome-only
and GPU-sensitive signals are logged, not asserted, so a software-WebGL CI host
doesn't false-red. Validated against firefox-10: full e2e 130 green.
2026-06-11 20:19:19 +02:00
feder-cr
3f2834d8c2 ci: auto-generate release notes from the invisible_firefox commits
The publish job used a fixed body that still read 'DRAFT - do not publish' on the
live release and listed none of the actual changes. Now the body is built from the
source commits that went into the binary: the build records which invisible_firefox
commit it came from (source-commit.txt), and publish diffs that against the previous
release's recorded commit via the GitHub compare API (no deep clone, no cross-repo
token) to list the user-facing subjects. docs/chore/ci/test commits are filtered out,
and the body ends with 'Built from invisible_firefox @<sha>' for traceability. It's
still a draft - the realness gate and the un-draft flip stay manual (issue #14).
2026-06-11 19:14:45 +02:00
feder-cr
b3608771ed release: pin the wrapper to firefox-10
firefox-10 is published: the in-binary headless window cloak (Windows DWMWA_CLOAK,
macOS NSWindow alpha-0, Linux keeps Xvfb) and the WebGL readPixels gamma remap that
clears pixelscan's masking flag. Validated against the built binary — validate_release
(linux+win), full e2e (128 passed), fppro ALL CRITICAL CLEAN, consistency PASS, and the
cloak gate green on all 5 targets (macOS via CGWindowAlpha).
2026-06-11 18:29:07 +02:00
feder-cr
c9cc0f1743 test(mouse): widen hover->mouseenter wait to 10s (full-suite load flake)
test_hover_triggers_mouseenter timed out at the old 5s wait when run as part of
the full e2e suite — browser startup + CPU contention occasionally push the
mouseenter past the window. In isolation the event fires in well under a second
(5/5), so this is load-sensitivity, not a real regression. A 10s wait absorbs it
while still failing fast if mouseenter genuinely never fires.
2026-06-11 18:08:11 +02:00
feder-cr
a950537f0a ci: macOS gate tolerates the runner's missing WebGL; add verify-cloak
The firefox-10 build gated green on all 5 targets but both macOS gate legs
failed. The cloak/webgl guards hard-required a live WebGL context, and macOS
GitHub runners expose none in the CI session (no software-GL fallback, unlike
Linux llvmpipe and Windows WARP). The cloak renders fine there anyway, which the
non-blank screenshot proves, so on the mac legs the WebGL-present check now
self-skips and the cocoa cloak is validated via the screenshot plus CGWindowAlpha.
The gamma masking guard skips on mac too (platform-agnostic C++, covered on Linux
and Windows).

verify-cloak.yml re-runs these guards against a prior build run's artifacts with
no rebuild, so a test-only fix like this is validated against the real binaries
in minutes instead of a 3h rebuild.
2026-06-11 17:18:02 +02:00
feder-cr
d4db15d37b ci: install the [dev] extra (pytest) in the release cloak/webgl guard step
The gate runner only had Playwright; `pip install -e .` doesn't pull pytest (a
dev dep), so `python -m pytest` failed with "No module named pytest". Install
".[dev]" like e2e.yml does.
2026-06-11 14:29:31 +02:00
feder-cr
c2103ed0db headless: cloak on Windows/macOS, Xvfb on Linux; CI cloak + webgl-masking guards
headless=True now hides the window via the binary's own cloak pref
(zoom.stealth.cloak_windows) on Windows and macOS instead of the broken
thread-level SetThreadDesktop; macOS is now supported. Linux keeps Xvfb.

Adds e2e guards that also run per-platform in the release drive-gate:
- test_cloak: the window is hidden (Windows DWMWA_CLOAKED / macOS CGWindowAlpha)
  yet still renders + drives; the macOS leg is where the cocoa cloak patch runs.
- a WebGL readPixels masking guard: the gamma noise must stay a smooth gamma
  remap, not the pixelscan-maskable +-1 spikes.
2026-06-11 11:58:14 +02:00
feder-cr
e524695088 fix(webrtc): ship the validated proxy realness config + CI guards
Audit follow-up (2026-06-10), all validated before commit.

#2 WebRTC — the shipped baseline now MATCHES the manually-validated config
(behind a residential proxy: host=<uuid>.local, srflx=proxy egress, No-Leak,
gathering completes, indistinguishable from vanilla Firefox on BrowserLeaks +
CreepJS):
  - prefs baseline obfuscate_host_addresses False->True; add
    zoom.stealth.webrtc.disable_ipv6=True; drop the dead
    media.peerconnection.ice.disableIPv6 (no-op on FF150)
  - launcher auto-derives the proxy egress IP via _geo.prepare_session_geo
    (one round-trip shared with the timezone resolution) and feeds nICEr via
    STEALTHFOX_WEBRTC_PUBLIC_IP + STEALTHFOX_WEBRTC_DISABLE_IPV6 in _build_env
    (sync + async); an explicit caller env still wins. The C++ mechanisms were
    already in firefox-9 — this activates them, no rebuild.

#1 drop orphan prefs zoom.stealth.timezone + zoom.stealth.seed (read by no C++;
   the live ones are juggler.timezone.override + zoom.stealth.fpp.hw_seed).

#3 release title 'rev N' instead of 'rev firefox-N'.

CI guards (unit, leak-safe — no real proxy/creds, the kind that would have
caught this gap at zero cost):
  - shipped-baseline guard + no-orphan-prefs (test_webrtc_realness.py)
  - egress auto-derive in _build_env (test_launcher_helpers.py)
  - prepare_session_geo returns (tz, egress) (test_geo.py)
CI keeps faking 'behind a proxy' with an in-process TCP-only SOCKS5 + RFC 5737
TEST-NET IPs; real-proxy residential realness stays a LOCAL manual gate.

449 unit pass.
2026-06-10 14:30:16 +02:00
feder-cr
584ad97179 docs: README lists all 5 supported platforms + fetch --force
firefox-9 ships linux x86_64/arm64, windows x86_64 and macOS arm64/x86_64
since the CI pipeline; the README still said Windows+Linux only. Also
document the macOS quarantine step (ad-hoc signed, not notarized) and the
fetch --force flag added with firefox-9.
2026-06-10 11:10:49 +02:00
feder-cr
12883bb4c7 docs: correct e2e count 138 -> 127 in e2e.yml + run_e2e.py comments 2026-06-09 18:29:58 +02:00
feder-cr
ef86cd57dc test(e2e): use the full BotD + FpJS API (per-detector + components)
Maximize what the real detectors exercise on CI:
- BotD: assert the aggregate detect().bot==false AND every individual detector
  via getDetections() (webDriver/userAgent/appVersion/plugins/process/...). This
  restores the per-detector granularity of the deleted hand-rolled test_botd_*,
  but from the real library, with diagnostics on which detector flagged.
- FpJS: visitorId present + stable across two launches, AND a rich component
  surface (>=15 signals — a suppressed/thin surface is itself a tell). We don't
  hard-assert zero errored components (some are legitimately unsupported per
  browser); visitorId stability is the authoritative aggregate check.

All 3 green locally against firefox-9.
2026-06-09 18:15:25 +02:00
feder-cr
2410582960 test: drop the 15 hand-rolled test_botd_* (real BotD now runs on CI)
These mirrored BotD's individual detectors (webdriver/appVersion/userAgent/
functionBind/productSub/process/evalLength/languages/plugins/mimeTypes/
distinctive-props/documentAttributes/windowSize/webGL) — our reverse-engineering
of BotD from before we ran the real library. Now that test_detectors_e2e.py runs
the actual @fingerprintjs/botd against the binary on CI (asserts bot===false),
they're redundant. Kept the complementary/unique tests: fpjs-surface, pinning,
sannysoft + fpscanner (which mimic detectors we do NOT run, so not covered by
BotD/FpJS), and the determinism/consistency suite.
2026-06-09 18:02:36 +02:00
feder-cr
df4493d553 test(e2e): run the real detectors (BotD + FingerprintJS OSS) on CI
Instead of only our hand-rolled signal checks, load the actual MIT detection
libraries against the patched binary and assert it isn't flagged:
- BotD (the client-side bot detector FingerprintJS Pro itself uses): detect()
  must return bot=false (no automation/headless tell).
- FingerprintJS OSS: visitorId present and stable across two fresh launches
  with the same seed (drift = per-session entropy = a bot tell).

Hermetic: the libs are vendored (tests/vendor/, pinned, MIT) and served from a
localhost server — no external CDN (Firefox tracking-protection blocks it
anyway), no IP/network dependency, runs identically on a dev box and the GitHub
runner. Both green locally against firefox-9.
2026-06-09 17:53:11 +02:00
feder-cr
8ba88958be test(e2e): hermetic SOCKS5 auth + routing e2e (runs on CI)
Proves the patched nsProtocolProxyService end to end: the binary performs the
RFC1929 user/pass handshake with the configured socks_username/password and
relays the page through the proxy — something Playwright's own proxy= can't do,
and which test_proxy only unit-checks at the pref level.

Fully hermetic so it runs identically locally and on the GitHub runner: a local
SOCKS5 server (requires auth, records the creds it saw) + a local HTTP target,
with the localhost target forced through the proxy via allow_hijacking_localhost
+ no_proxies_on="". No external site, no secrets. 3/3 local.
2026-06-09 17:37:40 +02:00