Commit Graph
97 Commits
Author SHA1 Message Date
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
feder-cr 4564b26158 test: stabilize 2 e2e for CI (hover wait, webrtc srflx env-skip)
Running the full e2e on GitHub (xvfb) surfaced 2 env-sensitive failures, neither
a binary bug:
- test_hover_triggers_mouseenter read window.__h immediately after hover(); the
  mouseenter can land a beat later on a virtual display. Use wait_for_function
  (still fails if the event genuinely never fires). 5/5 locally now.
- test_not_blocked_behind_tcp_only_socks needs a remote origin loaded fully
  through the proxy to inject the synthetic srflx; that path is environment-
  sensitive on a datacenter CI box. Keep the hard "zero candidates = blocked =
  FAIL" check, but skip (not fail) if the srflx didn't engage — validated locally.
2026-06-09 17:23:46 +02:00
feder-cr 036a1a1d5f ci: run the full e2e browser suite on GitHub (linux, xvfb, every push/PR)
Maximize what's tested on CI: a new e2e job fetches the public firefox-9 binary
and runs all 138 @pytest.mark.e2e via scripts/run_e2e.py under xvfb-run. The
wrapper launches Firefox HEADED on a (virtual) display, which is stable on the
hosted runners — unlike the true-headless drive-gate that flaked. Secret-free
(public binary + local fake-SOCKS for webrtc), so safe in public CI; the proxy
realness gate (fppro) stays local. Trial-running now; will tune if xvfb/timeouts
need it.
2026-06-09 17:03:43 +02:00
feder-cr 0b53e18e23 ship: bump BINARY_VERSION to firefox-9 + refuse the broken firefox-8 (audit B5)
firefox-9 is published (build 5/5, drive-gate 5/5, full e2e 137/1-skip, fppro
ALL CRITICAL CLEAN, WebRTC real, sha256-verified download path). Point the
wrapper at it.

Guard: ensure_binary() now refuses any version in BROKEN_VERSIONS (={firefox-8})
with a clear error instead of handing over an undrivable binary — a cached
firefox-8 from before this bump would otherwise keep being used silently. Tests:
the current BINARY_VERSION can never be in BROKEN_VERSIONS; firefox-8 stays
flagged; ensure_binary("firefox-8") raises.
2026-06-09 16:02:52 +02:00
feder-cr 62cdf626a0 ci: pin actions to SHA + single-source the playwright pin (audit B6/B4)
B6: pin every third-party action in the build/publish path to an immutable
commit SHA (a retagged actions/checkout or action-gh-release would otherwise
inject code into the binary users download). The other workflows (tests, webrtc,
launch-matrix) handle no secrets, so they're left on tags.

B4: the playwright pin lived in two workflow files with no shared source. Move
it to scripts/playwright_pin.txt that both read, so they can't drift. The drive
gate already ENFORCES playwright<->juggler compatibility (an incompatible pin
fails the launch/drive and nothing publishes); the file is the single bump point
when the juggler is re-synced.
2026-06-09 15:59:18 +02:00
feder-cr 5dac302938 test: activate the full e2e (browser-driving) suite + add fetch --force
The 138 @pytest.mark.e2e tests were doubly inactive: deselected by addopts AND
skipped without a cached binary — and 3 of the 6 per-file firefox_binary
fixtures silently ignored INVPW_BINARY_PATH, so they'd test whatever was cached
even when you pointed the suite elsewhere (a false-confidence trap).

- Centralize firefox_binary into conftest.py (env INVPW_BINARY_PATH → cache →
  skip); delete the 6 duplicates. Unify test_webrtc_realness onto the same env.
- scripts/run_e2e.py: one command that runs ALL e2e against a chosen binary,
  with reruns so an under-load interaction flake (dblclick/hover pass 3/3 in
  isolation) self-heals while a real break fails every attempt. The webrtc e2e
  fake a TCP-only SOCKS locally, so the suite is offline. This is the MANDATORY
  pre-release browser gate (local — hosted runners are too interaction-flaky).
- Running the suite against firefox-9 surfaced a real gap: `invisible_playwright
  fetch --force` was unrecognized (the subparser took no args) though the e2e
  test + docstring expect it. Implement it: drop the cached version dir, refetch.
- Add pytest-rerunfailures + playwright to the dev extras.

Baseline against firefox-9: 136 passed, 1 skipped (linux_only on win host),
1 was the --force gap now fixed.
2026-06-09 15:40:02 +02:00
feder-cr 67b5e7cd5e ci: split drive gate — smoke on all 5 legs, full interaction on linux-x86_64
The free hosted runners (windows-latest worst) are content-process unstable
under a heavy headless interaction sequence: clicks/moves cascade into
context-destroyed / selector-timeout / eval-CSP, even across 3 retries, even on
linux-arm64. That's an environment limit, not a binary defect (the binaries
drive 20/20 locally and the stable legs pass).

So: SMOKE (launch + http page + UA + webdriver + DOM read) runs on all 5 legs —
the firefox-8/juggler catcher, robust everywhere. FULL (+ mouse/keyboard/canvas/
navsurface, the firefox-2 class) runs only on linux-x86_64; the interaction code
is platform-identical JS (omni.ja), so one reliable full run covers every
platform, and win interaction stays covered by local pre-release testing.
2026-06-09 15:01:21 +02:00
feder-cr 5f546f4d63 ci: serve drive-gate page over loopback HTTP + retries (robust on win-CI)
windows-latest headless kept flaking on special-scheme pages: data: URLs get
re-normalized (re-nav), about:blank + redundant goto destroys the context, and
both can carry a CSP that blocks eval(). Serve the test page over a real
http://127.0.0.1 instead — none of those quirks, and it adds real-navigation
coverage (await a response). Evaluates stay arrow-functions (no eval), listeners
are inline-script (no on* attrs), and transient context-destroyed/detached/timeout
gets up to 2 retries. A genuinely broken binary fails all 3 attempts.
2026-06-09 14:53:05 +02:00
feder-cr 2dd2224e73 ci: drive gate uses arrow-function evaluates (fixes win-CI eval-CSP block)
windows-latest failed both attempts with "Page.evaluate: call to eval() blocked
by CSP". Bare-string evaluates (page.evaluate("navigator.userAgent")) make
Playwright fall back to eval(), which a page CSP blocks. Pass arrow functions
instead (called via callFunction, never eval'd), and wire the click listener
with addEventListener instead of an inline onclick (also CSP-sensitive). Both
are Playwright best practice and platform-agnostic.
2026-06-09 14:47:02 +02:00
feder-cr 610f09d2c2 ci: build drive-gate DOM on about:blank, not a data: URL (fixes win-CI flake)
linux+macOS drive went green but windows-latest kept throwing "execution
context destroyed by navigation" at a wandering evaluate (passed 20/20 win-local,
no browser crash logged). Root cause: the unencoded data: URL gets re-normalized
(re-navigated to its percent-encoded form) by Firefox; the slower win runner
races that re-nav against the evaluates. about:blank is canonical and never
re-navigates, so the DOM is now built there via innerHTML. Also add one logged
retry on transient context-destroyed/detached (a broken binary fails both).
2026-06-09 14:42:26 +02:00
feder-cr 90529ff181 ci: de-flake drive gate — drop the racy iframe probe, keep input/canvas checks
Re-running the enriched gate on the real binaries exposed a ~1-in-5 flake: the
iframe probe (nested data: / srcdoc) re-navigates, so Juggler's frame id changes
mid-check ("execution context destroyed" / "Frame was detached"). set_content
isn't an option either — this build rejects its document.write ("operation is
insecure").

Drop the iframe from the gate: a same-origin srcdoc iframe is a weak proxy for
the cross-origin issue #20 anyway. The page is now a plain subframe-free
goto(data:), and the mouse/keyboard/canvas-determinism/navigator-surface checks
(the firefox-2 class + stealth smoke) stay. 20/20 clean locally. The faithful
#20 sentinel (tests/test_cross_origin_iframe.py, two localhost origins) should
be wired as its own e2e gate job.
2026-06-09 14:35:50 +02:00
feder-cr 8d7b6eafdf ci: drive gate exercises mouse/keyboard/iframe/canvas, not just navigate+eval
An adversarial audit of the pipeline found the drive gate only did goto+evaluate,
so several historically-shipped breakages would still pass it green:
- firefox-2 (jugglerSendMouseEvent missing) — no mouse/keyboard was tested
- issue #20 (cross-origin iframe content_frame() None) — no iframe was tested
- canvas non-determinism (stealth seed) and headless navigator tells

ci_drive_gate.py now clicks a button, moves the mouse, types into an input,
reaches into an iframe, checks an identical canvas draw is byte-stable, and
checks navigator.languages/plugins — all offline (data: URLs), GPU-free, no
proxy. Validated against the real build.

Pipeline hardening from the same audit:
- Windows: stop swallowing `mach package` failure and never fall back to the
  dev tree dist/bin (that masked the firefox-7/8 packaging bugs)
- macOS: plutil -lint Info.plist + required-key checks (a malformed plist ships
  fine through a headless drive but Finder calls the .app "damaged")
- publish: assert all 5 archives present + fail_on_unmatched_files (no silent
  partial release if a build leg drops out)
2026-06-09 12:53:42 +02:00
feder-cr 7260f461bb ci: verify-assets needs contents:write to read draft releases
gh release download 404s ("release not found") on a draft tag when the token
is contents:read — GitHub only shows drafts to tokens with push access. The
workflow still only reads assets; the scope bump is purely for draft visibility.
2026-06-09 12:29:35 +02:00
feder-cr 86a04d2d34 ci: drive-test every release binary via Playwright, not just screenshot
The old gate ran firefox --headless --screenshot, which renders fine even
when the juggler automation layer is missing from the package — so a binary
Playwright can't actually drive (firefox-8) passed and shipped broken.

Replace it with a real drive gate: a 5-leg matrix that launches each binary
over the juggler pipe on its native runner, loads a page, and round-trips JS
(also asserts navigator.webdriver stays hidden). Headless and no screenshot,
so it stays GPU-free on the hosted runners and needs no proxy or secrets.

Same logic is reusable standalone via verify-assets.yml to drive-test an
existing release's assets without a rebuild.
2026-06-09 12:24:06 +02:00
feder-cr eec373a719 ci: $0 5-target GitHub Actions release pipeline + wrapper macOS/arm64 support
release.yml builds linux-x64/arm64 + win-x64 (cross) on free Linux runners and
macos-arm64/x64 on native Mac runners; packages per the wrapper contract
(juggler-gated so binaries are Playwright-drivable, issue-#14 symlink-safe via
cp -aL), validate_release.py gate, ad-hoc macOS codesign, DRAFT publish.
constants.py: arm64 + darwin ARCHIVE_NAME + BINARY_ENTRY_REL (Firefox.app).
download.py: macOS post-extract xattr quarantine strip.
BINARY_VERSION unchanged (firefox-8); the juggler-fixed firefox-9 is a separate
release cut + pin bump.
2026-06-09 10:45:27 +02:00
Federico 215b8801d7 Update README.md 2026-06-08 06:16:49 +02:00
feder-cr cc7d95c8ae release: pin BINARY_VERSION to firefox-8
firefox-8 carries the WebRTC fixes: behind a proxy, ICE now completes with an
mDNS .local host and a server-reflexive candidate on the proxy IP (genuine
nICEr priority/foundation) instead of coming up blocked, and IPv6 host
candidates are suppressed. Binary published on the releases page; validated on
both Windows and Linux via scripts/validate_release.py.
2026-06-06 22:34:20 +02:00
feder-cr 8bf72da40c test(webrtc): realness sentinels + e2e behind a fake TCP-only SOCKS proxy
Unit sentinels (run in CI) assert the rules a real WebRTC profile must meet:
host candidate is mDNS .local (never a raw LAN IP), the synthetic srflx carries
the egress IP with a genuine nICEr priority (rejecting the old local_pref
0xFFFF) and a foundation distinct from the host one, and CreepJS's resolver
returns the egress (and reads a host-only SDP as blocked).

e2e tests launch the binary and check the live gather. "Behind a proxy" is
reproduced without any external proxy: an in-process SOCKS5 server relays TCP
CONNECT but refuses UDP ASSOCIATE (a TCP-only residential proxy), and the
egress IP is injected as an RFC 5737 TEST-NET address.
test_not_blocked_behind_tcp_only_socks guards the gather-fails-behind-proxy bug.

webrtc-e2e.yml runs the e2e on demand (needs a binary that carries the fixes).
2026-06-06 18:39:03 +02:00
Federico e2bcd0cd4c Update README.md 2026-06-06 15:24:23 +02:00
Federico 6f44e1af38 Update README.md 2026-06-06 15:19:45 +02:00
Federico 262d388b99 Update README.md 2026-06-06 07:51:46 +02:00
Federico b7eda606a2 Update README.md 2026-06-06 07:50:00 +02:00
Federico e3b8a42ded Update README.md 2026-06-06 07:48:19 +02:00
Federico 26fa962d24 Update README.md 2026-06-06 05:45:37 +02:00
Federico 7b860b7398 Update README.md 2026-06-06 05:43:55 +02:00
feder-cr f2664f96e1 feat: timezone="auto" resolves from any egress + weekly geoip auto-update
Refine timezone="auto" so it ALWAYS resolves (drop the "host" sentinel):
- ""/"auto" resolve from the proxy egress when a proxy is set, else from the
  host own public IP (direct lookup); an explicit zone is the only opt-out.
- on failure: with a proxy raise; without a proxy fall back to the host TZ.

GeoIP DB now auto-updates against daijro/geoip-all-in-one weekly rebuild:
cache the latest, re-check after GEOIP_REFRESH_DAYS (7), prune old tags,
reuse a stale cache offline; GEOIP_MMDB_VERSION is only the cold fallback.

tests: test_geo.py (37) + test_geoip_update.py; full unit suite 429 green
plus 8 live combinations (proxy / no-proxy / explicit / failing / freshness).
2026-06-06 05:16:20 +02:00
feder-cr 369f3f7fdb docs: document timezone="auto" in API docstrings + CHANGELOG
Follow-up to the timezone="auto" feature. The launcher/config timezone
docstrings still said "empty means host TZ", now incomplete since
""+proxy defaults to auto.

- launcher timezone arg: full precedence (auto default, host escape
  hatch, fail-early on unresolvable zone behind a proxy)
- config.get_default_stealth_prefs: note it does NOT resolve "auto"
  (pure pref builder, no proxy/network context)
- CHANGELOG: Unreleased entry for timezone="auto" + new deps
2026-06-06 04:43:40 +02:00
feder-cr d6c3de7730 feat: timezone="auto" derives the zone from the proxy egress IP
A proxy in a different country paired with the host timezone is the
classic timezone_mismatch signal, so a session with a proxy and no
explicit timezone now resolves the zone automatically.

- discover the egress IP through the proxy (SOCKS via requests[socks]),
  map it to an IANA zone with an offline mmdb (daijro/geoip-all-in-one,
  downloaded + cached like the Firefox binary; GPL so not vendored)
- precedence: explicit zone wins; ""+proxy and "auto"+proxy resolve;
  ""/"auto" without a proxy stay host; "host"/"local" force host TZ
- fail-early when a proxy is set but the zone cannot be resolved, never
  a silent host-TZ fallback
- deps: requests[socks], maxminddb, tzdata (zoneinfo ships no DB on Windows)
- resolve_session_timezone / ensure_geoip_mmdb exported for integrations
2026-06-06 04:16:22 +02:00
Federico 143aff4bd2 docs: warn about true-headless gotcha in public config API (#27)
Live smoke test caught a footgun: passing headless=True directly to
playwright.firefox.launch() with our prefs puts Firefox in true
headless mode (no rendering pipeline) which breaks canvas/audio/WebGL
fingerprint coherence. InvisiblePlaywright translates user-facing
headless=True to Playwright headless=False + virtual display
automatically; the new public helpers do not, so the docstring +
README now flag this explicitly.

Verified: same prefs + headless=False via firefox.launch() reaches
bot.sannysoft.com with 23 passed / 0 failed, matching what
InvisiblePlaywright produces.
2026-05-28 17:19:26 -07:00
Federico ee0fe57ced feat: public config helpers for third-party integrations (#25)
Adds invisible_playwright.config module with:
- get_default_stealth_prefs(seed, *, pin, locale, timezone,
  extra_prefs, humanize, virtual_display) -> dict
- get_default_args() -> list

Both also re-exported at the package root alongside the existing
InvisiblePlaywright. ensure_binary is also re-exported there for
parity with the cloakbrowser.download.ensure_binary integration
pattern that downstream projects (Skyvern PR #5340, crawlee-python
PR #1794, agno PR #8129) already expect.

These helpers let third-party fetchers (changedetection.io plugins,
Crawlee BrowserPool subclasses, agno toolkits) drive
playwright.firefox.launch(executable_path=..., firefox_user_prefs=...)
themselves without depending on the InvisiblePlaywright context
manager owning the lifecycle. Same seed semantics, same humanize
toggle, same extra_prefs overlay as the existing wrapper.

Tests: tests/unit/test_config_public.py adds 14 unit tests covering
deterministic seed, locale/timezone/pin/extra_prefs/humanize
variations, and round-trip via the public namespace. Full unit suite
(392 tests) stays green.

Backwards compatible: InvisiblePlaywright surface is unchanged.
BINARY_VERSION stays at firefox-7. Python-only release.
2026-05-28 17:05:22 -07:00
feder-crandClaude Opus 4.7 929da150bc chore: bump version to 0.1.9
Carries the #24 fix (importlib.metadata-driven __version__ + top-level
--version flag) so users see a real version bump after `pip install --upgrade`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:24:05 -07:00
feder-crandClaude Opus 4.7 66c6b09821 fix: __version__ comes from package metadata; add --version flag (#24)
Two bugs reported in #24:

1. `python -m invisible_playwright version` printed the literal "0.1.0"
   regardless of the installed version. Root cause: __version__ in
   __init__.py was hardcoded and never bumped when the package version
   moved past 0.1.0. Fix: read from importlib.metadata so __version__
   stays in lockstep with pyproject.toml's `version` field by construction.

2. `python -m invisible_playwright --version` errored with "the
   following arguments are required: cmd". Root cause: the parser had
   `required=True` on its subparsers and no top-level --version flag.
   Fix: add a top-level `--version`/`-V` flag using argparse's standard
   version action, drop `required=True`, and reroute the "no subcommand"
   case through parser.error() so the existing test_no_subcommand_errors
   contract is preserved.

7 new unit tests pin both behaviours so they can't regress silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:18:03 -07:00
feder-crandClaude Opus 4.7 f208f5262c docs(README): drop LLM-style typographic characters
Replaced em-dashes (—) with commas, colons, or periods depending on
context. Kept all emoji (✅/❌/⚠️) in the comparison table since
those are scannable scoring cues, not stylistic.

Net cleanup: 6 em-dashes removed from tagline, hero alt-text,
"Why it's powerful" paragraph, and the comparison intro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:57:29 -07:00
feder-crandClaude Opus 4.7 35508595fa docs(README): drop "(binary only)" qualifier on CloakBrowser row
The Closed source label is enough; the parenthetical was extra noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:54:23 -07:00
feder-crandClaude Opus 4.7 97a3cdfc17 docs(hero.gif): remove top dark band, top-align screenshots
Previous build still had ~25px of dark padding above the screenshot
plus a thin shadow border around it. Both gone now. Screenshot is
flush against the top edge of the frame and runs full-width; the only
remaining chrome is the 90px caption bar at the bottom with the green
checkmark and detector verdict.

Net effect: roughly 25-30% more usable screenshot area per frame, no
visible "bar" above. Size 478 KB (was 381 KB, larger because the
embedded pixels are now bigger).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:51:51 -07:00
feder-crandClaude Opus 4.7 033d0e9b35 docs(hero.gif): drop top brand bar, enlarge screenshot area
The "invisible_playwright" / github URL strip at the top of the
hero was redundant - the README headline right above the embed
already says both. Removing it frees vertical pixels for the actual
screenshot, which is what the visitor is here to see.

Layout now: screenshot fills everything above the 90px caption bar
at the bottom. Each screenshot renders ~10% larger.

Size 381 KB (was 356 KB - slightly larger because the screenshots
themselves are bigger after the scale change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:48:06 -07:00
feder-crandClaude Opus 4.7 def731e6ec docs(README): drop disclaimer + closing pitch after comparison table
The two trailing paragraphs (testing disclaimer and the
"if you need Firefox + active maintenance" wrap-up) restated what
the table already showed. The table is the comparison; let it stand
on its own and jump straight to Install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:38:31 -07:00
feder-crandClaude Opus 4.7 4a71a0142a docs(README): flag CloakBrowser as closed source
The previous wording lumped CloakBrowser in with Camoufox as
"open-source peers." That was wrong: CloakBrowser publishes a Chromium
binary plus a wrapper, but the C++ source patches that produce the
binary are not in the repo. From a user's standpoint that's the same
trust profile as a closed-source commercial fork.

Comparison table updated to reflect this: Open source column now
distinguishes Camoufox (MPL, full source) and ours (MIT, full source
in invisible_firefox) from CloakBrowser (binary only) and the four
SaaS competitors (closed).

Intro paragraph rewritten to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:19:13 -07:00
feder-crandClaude Opus 4.7 9432e789c6 docs(README): drop 6-bullet hook under hero
The hero GIF already shows each detector with its verdict in-frame
(reCAPTCHA 0.90, CreepJS 0 lies, FingerprintJS Pro not detected,
WebRTC no leak, sannysoft all green). Repeating the same five points
as a bullet list immediately below the visual was duplication that
pushed the comparison table and install snippet further down.

Above the fold is now: tagline, hero GIF, then straight into Why
it's powerful.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:17:38 -07:00
feder-crandClaude Opus 4.7 75e6927904 docs(README): drop Results section
The hero GIF above the fold already lists each detector with its
verdict in-frame. The Results section was repeating the same five
items in prose right after the visual, which is the kind of
above-the-fold padding that pushes the install snippet further down
without adding signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:43:57 -07:00
feder-crandClaude Opus 4.7 f4d42dcac4 docs(README): add hero.gif above-the-fold + consolidate Results section
Hero is a 5-frame slideshow (12.5s loop, 356KB, 1200x675) cycling
through the five detection-test screenshots with a green-check
caption per frame. Branded with the project name top-left and a
github URL top-right so it works when embedded in tweets or blogs
without context.

Sits immediately after the tagline so the first thing a visitor sees
above the fold is moving visual proof, before any text. Industry
research on top-performing OSS READMEs in 2026 consistently puts
GIF/video first as the single biggest conversion lever for star intent.

Also collapsed the five expanded Results subsections down to a single
section: the GIF already shows each tester live, so the prose now
gives one short bullet per detector plus links to the original
full-resolution screenshots for anyone who wants to inspect them.

Net effect: roughly 200 lines of vertical scroll removed above the
fold, hero visual added, deep-link to per-tester screenshot
preserved.

No new test surface. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:28:18 -07:00
feder-crandClaude Opus 4.7 0375cf3f79 docs(README): tighten positioning + benchmark vs camoufox and CloakBrowser
Three changes aimed at making the project's value legible in 5 seconds
for someone landing from a Trending list or HN thread:

- New tagline + quantified bullet hook at top. Concrete numbers
  (0.90 reCAPTCHA, 0 CreepJS lies, 5/5 detection suites passed) up
  front instead of generic "passes the hardest detectors" wording.

- Comparison table rewritten. The commercial-stack columns (Multilogin,
  GoLogin, AdsPower, Dolphin Anty, Kameleo) were noise for the OSS
  audience we want to reach. Replaced with the two relevant
  source-level peers: Camoufox (Firefox, currently in a long
  maintenance gap) and CloakBrowser (Chromium, fresh, but capped at
  the Chromium reCAPTCHA ceiling).

- "Why it's powerful" opens with explicit positioning vs the two
  peers, plus the reCAPTCHA v3 score that's the most defensible
  numeric claim.

No code change. Repo homepage URL set separately via API to deep-link
the "Why it's powerful" section so visitors from external links land
on the value pitch rather than the badges row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:26:12 -07:00
feder-crandClaude Opus 4.7 62b9030d2a ci: fix firefox --version check (stdout match, not exit code)
firefox.exe --version on Windows prints the version string but may
return non-zero exit code (sub-process fork quirk). The previous check
treated that as a launch failure, producing a false-positive failure
across the whole matrix while the binary actually launched cleanly.

Switch to matching the printed output instead, so we only fail when the
binary really can't start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:25:23 -07:00
feder-crandClaude Opus 4.7 60d13a2b6e ci: cross-Windows-edition smoke matrix for firefox binary launch
Triggered by issue #22 (firefox-7 SxS mismatch reported on Win11 26200
by jannusdorfer-create). Verifies the shipped binary launches cleanly
on every Windows runner GitHub offers: windows-2022, windows-2025,
windows-latest, across Python 3.11 / 3.12 / 3.13.

Each cell does the reporter's exact flow: fresh checkout, pip install
from source, python -m invisible_playwright fetch, then runs the
InvisiblePlaywright(seed=9128) snippet.

If all cells pass the bug is environment-specific to the reporter
(corporate edition, EDR, GPO). If any cell fails the same way we ship
a sidecar mozglue.manifest in the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:20:00 -07:00
feder-crandClaude Opus 4.7 3d8ba0b82c feat: deterministic reCAPTCHA cookie pre-seed via Bayesian browsing history
Adds opt-in helper that auto-injects coherent cookie history into every
BrowserContext created via new_context(). Content is fully deterministic
from the persona seed so a given seed always presents the same cookies
across sessions.

Composition (per persona, all derived from seed):
  - 5 cookies on .google.com (NID, CONSENT, SOCS, _GRECAPTCHA, ENID).
    Excludes 1P_JAR which was deprecated by Google in 2022. CONSENT
    `lang+region` token derived from the persona's IANA timezone
    (Europe/Rome -> it+IT, America/* -> en+FX, etc.). NID prefix
    broadened to 100-540 to cover historical versions.
  - Per-site cookies on 13-25 "visited" everyday domains, sampled from a
    Bayesian network conditioned on gpu_class - workstation/high_end
    personas trend toward dev/tech sites, low_end/integrated_old trend
    toward shop/news/reference. Each site contributes 1-7 cookies based
    on a `cookie_profile` tag. Cookie pool includes _ga, _gid, _clck,
    _clsk, __cf_bm, OneTrust/CookieYes consent, _fbp (Facebook Pixel),
    _dc_gtm_<id> (Tag Manager helper), __hssrc (HubSpot helper).

API:
    Stealthfox(seed=42, prep_recaptcha=True)

No per-call configuration: visited-sites + cookie composition all derived
from the persona seed via the Bayesian sampler.

Gated server-side: forced False if profile_dir is set (persistent profile
owns its own state). All expiries capped to 395 days per Chrome/Firefox
400-day RFC 6265bis-15 limit.

Bayesian integration:
  - New `derive_browsing_history(gpu_class, rng)` in _fpforge/_sampler.py
    (parallel to `derive_font_prefs`).
  - New data files: browsing_pool.json (50 site entries) and
    cpt_browsing_given_class.json (per-class probabilities).
  - Profile dataclass exposes `browsing_history` field.
  - _recaptcha_seed.py consumes Profile.browsing_history; receives
    timezone separately to derive CONSENT lang+region.

Also drops a dead Chromium-only e2e test that always skipped on our
Firefox-only wrapper.

Test coverage: 29 unit tests covering composition, profile recipes
(minimal/ga_only/ga_cf/ga_consent/ga_consent_clarity), determinism,
Chrome 400-day cap, Playwright field requirements, CONSENT lang
mapping (IT/DE/US/default), helper-cookie probability distributions,
end-to-end with real fpforge Profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:01:26 -07:00
feder-cr 413db06690 docs(README): fix browser-launches badge to show only the number
The per-asset shields.io endpoint rendered the asset filename next to
the count ('1 [launch.txt]'). Switching to the per-tag total endpoint
renders as just the integer.
2026-05-23 17:07:19 -07:00
feder-crandClaude Opus 4.7 70b6a54dbc docs(README): add browser launches badge
Pulls the github download_count of the companion repo's usage-counter
asset. Click-through lands on the invisible_firefox release where the
full disclosure of what the counter measures lives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:59:17 -07:00
feder-crandClaude Opus 4.7 bfccd61863 docs: rename source-fork repo references invisible-firefox -> invisible_firefox
The companion Firefox source-fork repo was renamed today from
feder-cr/invisible-firefox to feder-cr/invisible_firefox so the two
canonical project repos share the same underscore naming
(invisible_playwright + invisible_firefox).

GitHub redirects clones of the old URL transparently, so anyone with
an existing clone keeps working without changes. New clones go
through the underscore URL directly.

This commit updates all in-repo references (README, CHANGELOG,
CONTRIBUTING, SECURITY, ISSUE_TEMPLATE/config.yml) to the new name.
No code, no version bump, no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:09:27 -07:00
feder-crandClaude Opus 4.7 34aeb9601f docs: sanitize CHANGELOG #18 entry + remove obsolete Known issues section
Two-part cleanup:

- CHANGELOG.md #18 entry: rewrite the symptom description without
  naming the specific third-party site that originally reported it.
  The technical root cause and fix are unchanged.

- README.md: remove the entire "Known issues" section. Its only entry
  was the headless=True alt-desktop crash from #18, which was fully
  fixed in 0.1.7 / firefox-7. Leaving the workaround instructions in
  the README would have misled users into adopting them
  unnecessarily. New issues can be added back as they're found; the
  default state is "no known issues".

Pre-push: 402 unit + integration tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:23:34 -07:00
feder-crandClaude Opus 4.7 64eef4daff release: 0.1.8 - fix #20 cross-origin iframe regression (pref-only)
Pin fission.webContentIsolationStrategy=0 (IsolateNothing) in baseline
prefs. FF150 ships strategy=1 (IsolateEverything) by default, which
site-isolates every cross-origin iframe into a separate webIsolated
content process even when fission.autostart=False - different from
FF146 Playwright behavior. The parent's Juggler FrameTree then sees
the iframe placeholder with no docShell, no URL and a stale execution
context, so content_frame() returns None, frame.evaluate() throws
cross-origin permission errors, and frame_locator(...).click() times
out. Reproduced with a local cross-origin HTTP harness (two 127.0.0.1
ports = two SOP origins).

The fix is a single pref because that's the level the original Juggler
code paths assume - vanilla Playwright Firefox 146 ran with the looser
default. Disabling site-isolation costs nothing for a single-user
puppet browser; process-per-browser/profile isolation is unaffected.
Caller can A/B per session via
extra_prefs={"fission.webContentIsolationStrategy": 1}.

Tests: tests/test_cross_origin_iframe.py adds 4 unit sentinels (pref
in baseline, survives translate_profile_to_prefs, extra_prefs override
works) and 5 e2e sentinels that spin up two local HTTP servers on
random free ports and verify the four protocol operations that
regressed (page.frames URL tracking, content_frame(), frame.evaluate(),
frame_locator(...).locator(...)) plus dispatch_event('click')
end-to-end, for plain, sandboxed and titled iframes. A future FF
upgrade or A/B flipping the pref will fail the suite before shipping.

Verified clean: pytest -m unit (342 passed), fppro_full.py (ALL
CRITICAL FLAGS CLEAN), fppro_consistency.py (visitor_id stable).

BINARY_VERSION stays firefox-7 - Python-only release.

Issue: https://github.com/feder-cr/invisible_playwright/issues/20

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:05:02 -07:00
feder-cr cb3755cdd5 chore(issue-templates): split bug_report into 3 focused templates
Replaces the single bug_report.yml with 3 templates that each require
only the fields relevant to their bug category. The old form asked every
reporter for URL / selector / runnable repro snippet, which made no sense
for launch failures (no browser to navigate, no element to select) and
overshot for detection reports (proxy region matters more than selector).

  01-launch-failure.yml      browser or wrapper never reaches new_page
  02-site-or-action-bug.yml  browser starts, an op (click / navigate / eval) fails
  03-stealth-detection.yml   a detector flags the browser (FpJS, CreepJS, BotD, ...)

Each template asks for the env basics plus the fields specific to its
category. The site/action one keeps the strict requirements introduced in
the previous template revision (headless, proxy, profile_dir, URL, selector,
runnable repro). The detection one drops selector/profile and asks for
detector name + verdict paste + screenshot. The launch one drops URL /
selector / proxy and asks for install command + full traceback.

Also fixes the contact_links: the old "Bug in the patched Firefox itself"
link pointed to feder-cr/firefox-stealth, a repo that was deleted on
2026-05-19 when source moved to feder-cr/invisible-firefox. Updated to
the new URL and clarified the boundary with the new stealth detection
template (detection results stay in this repo, source-level C++/IDL
patches go in invisible-firefox).
2026-05-22 20:28:31 -07:00
feder-cr 9571c3049d chore(issue-template): require headless / proxy / profile / URL / selector / runnable repro
Re-shapes the bug report form so reporters cannot submit underspecified
tickets like #18 / #19 / #20 which all required multiple round-trips
asking for basic environment context. Every field is now required unless
explicitly marked optional.

New required fields:
- headless=True/False (or N/A) — many bugs only repro on Windows
  headless=True where we use a hidden alt-desktop
- Proxy region (no proxy / UK / US / other / datacenter) — site
  behavior is often geo-dependent (e.g. GDPR consent on UK only)
- Profile directory (transient / persistent) — affects cookie state
- Exact URL string (no more "the homepage")
- Exact selector / locator (for click / locator bugs)
- Runnable reproduction Python snippet (pre-filled with scaffold so
  reporters cannot submit "just open the link")

Existing fields preserved: version, OS, Python, expected, actual,
confirmations.

New optional fields: screenshot upload area, browser/Playwright DEBUG
logs textarea, free-form notes.
2026-05-22 19:49:48 -07:00
feder-cr 1701b34688 release: 0.1.7 - pin to firefox-7 (issue #18 real fix)
Bumps BINARY_VERSION to firefox-7 which ships the real fix for issue #18
(id.sky.com tab crash on Windows headless=True).

firefox-7 contents:
- Canvas2D getImageData stealth spoof moved from read-only mapped surface
  to JS Uint8ClampedArray writable buffer (commit 2e17b4871f93 in
  invisible-firefox). Fixes the segfault on GPU-backed canvases that hit
  during page unload.

Paired with the wrapper-side security.sandbox.content.level=4 workaround
shipped in 2e0adbd (this repo), the combination resolves both halves of
issue #18: cross-process navigation crash in loop AND segfault at close.

End-to-end tested with InvisiblePlaywright headless=True + UK proxy on
id.sky.com: page survives 30s+, no crash in loop, no crash at close.

firefox-6 was rolled back when its partial-fix hypothesis turned out to
be the wrong root cause. Skipping straight to firefox-7.
2026-05-21 20:42:00 -07:00
feder-cr b98455bf8a test: unblock pre-push hook collection
- pyproject.toml: norecursedirs for tests/playwright-upstream/, a vendored
  Microsoft Playwright test suite with its own pixelmatch API version
  mismatch. We run it explicitly when doing compat audits, not on every
  push. Default collection now ignores it so the pre-push hook (which
  runs the full default pytest collection) doesn't error out.
- tests/test_service_worker.py: replace em-dash with hyphen inside a
  bytes literal at line 91. Python rejects non-ASCII bytes literals
  with SyntaxError at collection time. Now collects cleanly.

Both were blocking unrelated pushes (e.g. the issue #18 fix in the
previous commit). Splitting them out so the issue #18 commit stays
focused.
2026-05-21 20:21:12 -07:00
feder-cr 2e0adbde33 fix: id.sky.com tab crash on Windows headless=True (issue #18)
Two-part fix for the wrapper-repo issue #18 (tab crash on id.sky.com
and similar sites with cross-process navigation):

Part 1 (this commit, wrapper-side):
- _WIN_VIRT_DESKTOP_WORKAROUNDS adds security.sandbox.content.level=4.
  When the Chromium sandbox runs at content level >4 (default 6) it sets
  STARTUPINFO.lpDesktop=kAlternateWinstation for the content process,
  putting it on a different desktop than the browser process. Combined
  with our hidden alt-desktop (CreateDesktop) for headless windows
  hiding, that means cross-process navigations (Adobe AppMeasurement
  triggers a new origin -> new content process) can't reparent windows
  across desktops; the new content process exits cleanly and Playwright
  fires page.on('crash'). Lowering content sandbox to 4 keeps content
  processes on the parent's desktop. Level 4 still blocks file/registry/
  network access; only the alt-winstation isolation is dropped, which
  is what the desktop bug requires.
- README.md adds a Known Issues section pointing at issue #18 with
  the workaround (headless=False or Linux+Xvfb) for users on wrapper
  versions before firefox-7.

Part 2 (separate commit in invisible-firefox@2e17b4871f93):
- CanvasRenderingContext2D::GetImageDataArray moved the stealth pixel
  noise from rawData.mData (read-only DataSourceSurface::Map) to the
  JS Uint8ClampedArray's backing buffer. The original write to a
  read-only mapped surface segfaulted on GPU-backed canvases during
  browser.close() teardown.

Verified end-to-end with InvisiblePlaywright headless=True + Evomi UK
proxy on id.sky.com: page survives, no crash in loop, no crash at
teardown.

Reporter: @gamefireat123-eng.
2026-05-21 20:20:58 -07:00
feder-cr cf59e98fa9 test: fingerprint surface + consistency e2e tests
96 e2e tests reproducing the canonical anti-bot / fingerprinting
libraries' checks against a local InvisiblePlaywright session
on about:blank (no network).

Surface (28 tests):
  - BotD: webdriver, app_version, UA tokens, function_bind, productSub,
    process, eval.length, languages, plugins, mimeTypes, distinctive
    window/document globals, html attributes, window size, webgl_debug
  - sannysoft: chrome consistency, permissions.query, iframe chrome,
    iframe languages
  - FpJS: canvas 2D, audio offline, color-gamut, color-depth
  - PIN-locked: screen.width/height, hw.concurrency, audio.sampleRate,
    audio.maxChannelCount
  - fpscanner: UA<->platform, no userAgentData on Firefox

Consistency (68 tests):
  - Math determinism: 17 transcendentals + Math.pow
  - Worker scope vs main: 5 navigator props via Blob worker
  - Iframe scope vs window: 4 props + screen
  - UA self-consistency: UA<->platform, UA<->oscpu, UA<->appVersion
  - Native function self-toString: 8 native APIs
  - AudioContext / WebGL determinism
  - Locale<->Intl: DateTimeFormat / NumberFormat / Collator
  - Descriptor shape lies: 16 navigator props (each must be a getter,
    not Object.defineProperty(value=...))
  - performance.timeOrigin + .now() monotonic
  - Window dimension invariants
  - Firefox UA invariants (vendor='', appName='Netscape',
    appVersion short form)

All marked @pytest.mark.e2e so they're excluded from the default
suite that the pre-push hook runs. Invoke explicitly:

    pytest -m e2e -v

Or against a local build:

    INVPW_BINARY_PATH=/path/to/firefox.exe pytest -m e2e -v

Sources: github.com/fingerprintjs/BotD, abrahamjuliot/creepjs,
fingerprintjs/fingerprintjs, antoinevastel/fpscanner,
niespodd/browser-fingerprinting, bot.sannysoft.com.

Verified: 95 passed, 1 skipped (Chromium-only invariant), 0 failed
against firefox-5 local build.
2026-05-21 13:47:14 -07:00
feder-cr 22b1171518 feat: persistent profile dir + C7 closure (firefox-5 / 0.1.6)
- Add profile_dir= kwarg to InvisiblePlaywright (sync + async).
  Maps to firefox.launch_persistent_context(); returns a BrowserContext.
  Cookies / localStorage / extensions / cache / prefs all persisted.

- Drop the firefox-4 era workaround that filtered locale + timezone_id
  out of the persistent kwargs.  firefox-5 ships the C++
  docShell.overrideTimezone IDL method (50 LOC patch in
  docshell/base/nsIDocShell.idl + nsDocShell.cpp, see patch.md
  section 19 in feder-cr/invisible-firefox), so per-realm overrides
  land without crashing the launch handshake.

- Bump BINARY_VERSION firefox-4 -> firefox-5.

- Sentinel unit tests added: persistent kwargs MUST include locale +
  timezone_id (defends against re-introducing the workaround) and
  must NOT include timezone_id when timezone="" is the "host TZ" sentinel.

Validation: smoke test against the local firefox-5 build, persistent
context UP in 21s (was 180s timeout), Intl.timeZone == Europe/London,
hardwareConcurrency / screen / DPR / locale all reflect the PIN.
2026-05-21 12:19:38 -07:00
feder-cr acd568f5d3 ci: add pre-push hook to block red pushes
git config core.hooksPath .githooks (one-time per clone).
Runs pytest with the default markers (unit + integration) and refuses
to push if anything fails. ~2s overhead per push.

Bypass for emergency WIP with --no-verify; never on release branches.
2026-05-20 13:25:08 -07:00
feder-cr 5f0ba5d659 test: fortress coverage for download + constants + e2e
#15 shipped because unit tests only covered text-mode sha256sum output.
This adds a comprehensive parser test matrix (binary mode `*` prefix,
mixed, CRLF, BOM, indent, trailing whitespace, multiple stars, empty,
comment-only, sha256sum -b coreutils format) plus the integration
sentinel test_ensure_binary_accepts_binary_mode_checksums that
reproduces #15 against the live wire format.

Also covered for the first time:
- _resolve_asset_url public/private branches, auth header propagation,
  asset-missing failure, HTTP 4xx propagation
- _download_file 200/404/500, parent mkdir, auth on api.github.com
  only (not leaking to CDN URLs)
- cache_root / cache_dir_for_version path shape and version isolation
- _parse_owner_repo malformed inputs and dash/underscore/dot repo names

ARCHIVE_NAME case-matrix (uppercase platform, lowercase machine),
unsupported arch rejection (i386, ppc64le, arm64), unsupported platform
rejection (darwin, freebsd), BINARY_ENTRY_REL <-> ARCHIVE_NAME invariant,
RELEASE_URL_TEMPLATE shape (https, placeholders, owner pointer).

New e2e tests (marker `e2e`, excluded by default):
clean venv install, fetch against live release, binary launch, real-site
Playwright sanity. This is the test suite that would have caught #15
end-to-end before publish.

Stats: 275 -> 327 unit tests (+52), 0 -> 6 e2e tests.
Controprova: rolling back the parser fix makes 9 of the new tests fail
with the exact "no SHA256 for ..." error from #15.
2026-05-20 12:20:11 -07:00
feder-cr a0b61d1abf chore: bump to 0.1.5
Marker release for the #15 checksum parser fix that landed on main.
First-time fetch was broken for every user since checksums.txt
started shipping with sha256sum's binary-mode `*` prefix.
2026-05-20 12:10:45 -07:00
feder-cr 567717dfd7 release: 0.1.4 - firefox-4 binary with Page.uncaughtError fix (#13)
Fixes #13: every page that threw an uncaught JS error (bunny.net is the
reporter's repro) crashed the Playwright client with
"TypeError: Cannot read properties of undefined (reading 'url')".

Root cause: upstream Playwright Juggler added a required `location` field
to the Page.uncaughtError event in their 2026-05-07 patch roll; our fork
was carrying the pre-roll schema in every firefox-N build, and any
Playwright client released after the roll read pageError.location.url
strictly.

Fix is in the patched binary (feder-cr/invisible-firefox@1ba55d93), JS-only
inside chrome/juggler/. xul.dll and firefox.exe are byte-identical to
firefox-3 — only the Juggler protocol files change.

BINARY_VERSION bumped firefox-3 → firefox-4.  Package version 0.1.2 → 0.1.4
(0.1.3 was never published to PyPI; the changelog entry is kept as
historical record of the firefox-3 binary release).
2026-05-20 07:05:23 -07:00
feder-cr f1f3148d8f release: 0.1.3 - firefox-3 binary with all C5-C7 fixes (Win + Linux from clean fork)
BINARY_VERSION bumped from firefox-2 to firefox-3.  Both archives on this
release are built from a clean clone of feder-cr/invisible-firefox#stealth/150
(the consolidated source-of-truth fork) at commit 68906f1f9c55.

Changes vs firefox-2:
  - Proper C++ jugglerSendMouseEvent (replaces JS workaround from 0.1.1)
  - C1+C2: setDownloadInterceptor re-landed
  - C4: 5 nsIDocShell stealth attributes
  - C5: launcher + wmain juggler-pipe handle inheritance (Win)
  - C6: juggler-navigation observer notifications
  - C7 (partial): nsIDocShell.languageOverride storage stub
  - First native Linux build with the full patch series (previous releases
    shipped a stale Linux archive copied from earlier builds)

Verified with the 4-test smoke gate on both Windows and Linux:
  - test_launch (pipe stays connected, gap C5 sentinel)
  - test_new_page (Page.ready fires, gap C6 sentinel)
  - test_mouse (jugglerSendMouseEvent C++)
  - test_stealth (navigator.webdriver=false + sannysoft check)
2026-05-19 21:08:55 -07:00
feder-cr 7f7a791564 docs: rename feder-cr/firefox-stealth refs to feder-cr/invisible-firefox
The companion C++ patches repo at feder-cr/firefox-stealth was deleted
2026-05-19 and the source-of-truth fork was renamed from feder-cr/firefox
to feder-cr/invisible-firefox.  The branch stealth/150 on the renamed
fork is now the single source of truth for all C++ patches.

GitHub's auto-redirect is in effect for the old URLs but is not a
forever guarantee, so update all references now.

Updated:
  README.md         × 2 hyperlinks  (intro paragraph + license section)
  CONTRIBUTING.md   × 3 references  (quick links, scope, bug reports)
  SECURITY.md       × 1 reference   (out-of-scope vulnerabilities)
2026-05-19 15:41:44 -07:00
feder-cr f7b5e86793 release: 0.1.2 — point BINARY_VERSION at firefox-2 (mouse fix shipped)
The 0.1.1 release shipped the source-level fix for issue #9 (every
mouse path failing on FF150) but kept BINARY_VERSION at firefox-1
because the archive itself hadn't been refreshed yet. firefox-2 is
now live on GitHub Releases with the JS hot-swap applied to both
the Windows zip and the Linux tarball; users picking up 0.1.2 will
fetch the patched archive on first run.

Archive integrity verified on both platforms before publishing
(Windows boot test, Linux file-level checks, 21/21 assertions).
2026-05-18 15:31:28 -07:00
feder-cr 589c848e07 fix: every mouse action failed on FF150 — jugglerSendMouseEvent was never landed (#9)
The Juggler JS in upstream Playwright calls win.windowUtils.jugglerSendMouseEvent
at four sites, but when the Juggler was ported FF146 -> FF150 the matching C++
patch to nsIDOMWindowUtils.idl + nsDOMWindowUtils.cpp was dropped. Result: every
page.mouse.*, page.click(selector), locator.click(), page.hover(), mouse.wheel()
threw "win.windowUtils.jugglerSendMouseEvent is not a function" on first call.

The fix is shipped in the patched Firefox source (feder-cr/firefox-stealth):
six call sites in juggler/protocol/PageHandler.js and juggler/content/PageAgent.js
were swapped to win.synthesizeMouseEvent — a Mozilla chrome-scope helper that is
already present in FF150. scrollRectIntoViewIfNeeded was also guarded at the two
PageHandler.js sites where it was called unconditionally on the FF150
_linkedBrowser, which no longer exposes that method.

This invisible_playwright release adds the regression suite in tests/test_mouse.py
(12 cases inspired by microsoft/playwright-python/tests/async/test_click.py),
the CHANGELOG, and the version bump. The patched Firefox archive on GitHub
Releases must be refreshed before users actually receive the fix; the
BINARY_VERSION bump to firefox-2 will land with that asset.

Reporter: @trob9 (issue #9) — provided ready-to-apply JS patches, 4-line minimal
repro, and confirmed reCAPTCHA v3 = 0.90 holds after the swap.
2026-05-18 14:45:01 -07:00
feder-cr 0ac0581747 chore: add community standards (CoC, contributing, security, templates)
Adds the seven files needed to pass the GitHub Community Standards checklist:

- CODE_OF_CONDUCT.md (Contributor Covenant 2.1)
- CONTRIBUTING.md (scope, dev setup, tests, PR rules; points users at
  feder-cr/firefox-stealth for C++/spoofing issues)
- SECURITY.md (private vuln reporting via GH advisories or email,
  out-of-scope items redirected to firefox-stealth / Mozilla)
- .github/ISSUE_TEMPLATE/bug_report.yml
- .github/ISSUE_TEMPLATE/feature_request.yml
- .github/ISSUE_TEMPLATE/config.yml (disables blank issues, links to
  security advisories, firefox-stealth, and discussions)
- .github/PULL_REQUEST_TEMPLATE.md
2026-05-18 11:46:16 -07:00
feder-cr e37a4bc102 docs: add LinkedIn badge 2026-05-16 17:16:14 -07:00
feder-cr 07701a901d Merge pull request #2 from christianbaumann/main
Add comprehensive test suite (270+ tests across 14 files)

- Bayesian network primitives (25 tests)
- fingerprint sampler (55 tests)
- Profile dataclass + pin system (43 tests)
- configure_proxy decision table (24 tests)
- Windows + platform-agnostic prefs (27 tests)
- CLI / download gap coverage (17 tests)
- Multi-module integration (12 tests)
- E2e launcher lifecycle (9 tests)
- launcher / headless / async_api (32 tests)
- Linux-specific prefs / headless (16 tests)
- Linux tar.gz download (4 tests)
- Linux integration pipeline (3 tests)
- Linux launcher e2e (4 tests)
- pytest markers + shared conftest

Resolves conflicts in pyproject.toml, test_prefs.py, test_proxy.py by
keeping the upstream additions and merging with the previous wheel-build
fix. 323 tests pass on Windows; 14 e2e tests deselected by default
(require patched Firefox binary).
2026-05-16 17:14:08 -07:00
feder-cr 1d77f99770 docs: add Related projects section (arkenfox, LibreWolf, Camoufox) 2026-05-16 17:09:52 -07:00
feder-cr 9872b1995a docs: collapse layer table into a link to firefox-stealth 2026-05-16 10:49:32 -07:00
feder-cr 3a96103ab6 ci: remove PyPI publish workflow (not publishing) 2026-05-16 10:43:05 -07:00
feder-cr dd140d04ae docs: install via git+https (package not on PyPI) 2026-05-16 10:43:04 -07:00
feder-cr abaf798896 ci: add PyPI publish workflow on tag v* (uses trusted publishing, no token) 2026-05-16 10:39:41 -07:00
feder-cr 3d303a4915 tests: add wheel regression — fail if wheel has duplicate zip entries 2026-05-16 10:39:40 -07:00
feder-cr c60a1891b8 fix(build): remove redundant force-include that caused duplicate wheel entries (PyPI reject), add pytest slow marker 2026-05-16 10:39:39 -07:00
feder-cr b42dc31456 tests: add/update README.md 2026-05-15 20:02:02 -07:00
feder-cr 9de222aa15 tests: add/update tests/test_prefs.py 2026-05-15 20:02:01 -07:00
feder-cr 93da245b8a tests: add/update tests/test_cli.py 2026-05-15 20:02:00 -07:00
feder-cr 280d6838e1 tests: add/update .github/workflows/tests.yml 2026-05-15 20:01:59 -07:00
feder-cr fbfb4fc8c4 tests: add/update tests/test_launcher_config.py 2026-05-15 20:01:58 -07:00
feder-cr 03f8453398 tests: add/update tests/test_pin.py 2026-05-15 20:01:56 -07:00
feder-cr 4f38b2ec29 tests: add/update tests/test_fpforge.py 2026-05-15 20:01:55 -07:00
feder-cr 67e2824876 tests: add/update tests/test_proxy.py 2026-05-15 20:01:54 -07:00
feder-cr 508cb0f59d tests: add/update tests/test_imports.py 2026-05-15 20:01:53 -07:00
feder-cr 6185dbc078 docs: tighten technical claims (mDNS, Chromium open-source layers, competitor list, font metrics) 2026-05-15 19:52:25 -07:00
Federico c690dbfd33 Update README.md 2026-05-12 22:35:39 -07:00
feder-cr 7a983e99c5 feat: initial public release
invisible-playwright: a patched Firefox 150.0.1 for browser-fingerprint
stealth, shipped as a Playwright-compatible Python wrapper.

  * Sync + async InvisiblePlaywright launcher (firefox_user_prefs, virtual
    desktop on Windows, SOCKS5 auth via patched nsProtocolProxyService)
  * fpforge: Bayesian fingerprint sampler over GPU / audio / fonts /
    screen / ~400 other navigator fields
  * WebRTC stealth: srflx address swap, synthetic srflx fallback,
    private-LAN host candidates. No real public IP leak via STUN.
  * GPU sandbox fix for FF150 alt-desktop regression
  * Bezier-curve mouse motion baked into Juggler

Targets Windows x86_64 + Linux x86_64. Binary fetched on first run from
GitHub Release "firefox-1".
2026-05-12 22:32:58 -07:00