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>
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>
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>
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>
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.
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>
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>
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>
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>
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).
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.
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.
- 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.
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.
- 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.
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.
#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.
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.
sha256sum binary-mode output prefixes filenames with `*` and the parser was using parts[-1] verbatim, so checksum lookups by bare filename returned None and the wrapper raised RuntimeError instead of installing the binary.
Thanks LostBoxArt.
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).
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)
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)
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).
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.
E9-E12 exercise the launcher's Linux code paths without spawning a
real Firefox binary or Xvfb. They monkeypatch ``sys.platform`` and
stub ``make_virtual_display`` / ``_binary_on_path`` so the tests run
on any host:
- E9 ``_build_prefs(headless=True)`` on Linux passes
``virtual_display=False`` to the translator, so the Win32-only
``security.sandbox.gpu.level`` workaround never leaks into Linux
prefs (Xvfb handles window hiding instead).
- E10 ``_resolve_headless`` on Linux + headless=True invokes the
dispatcher and stores the returned object on ``self._virtual_display``.
- E11 ``_teardown`` stops the Linux virtual display, clears the
reference, and is idempotent on a second call.
- E12 With Xvfb missing from PATH, ``_resolve_headless`` raises a
clear ``RuntimeError`` mentioning ``Xvfb`` instead of a cryptic
FileNotFoundError.
Suite on Linux/WSL: 286 passed, 5 skipped (4 binary-gated E2E
lifecycle tests + 1 Win32 ctypes test). Binary-gated E1/E2/E5/E8
remain ready to run on Linux once the patched Firefox tar.gz is
fetched locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IT11–IT13 mirror IT10 on the Linux platform branch, verifying:
- Xvfb workarounds coexist with SOCKS5 proxy mutation
- MSAA pin propagates through prefs translation on Linux
- _LINUX_GENERIC_FONT_FACTORS is prepended to per-font metrics
Tests use monkeypatch on sys.platform so they run on any host OS.
Verified green on Linux/WSL alongside the existing Windows tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the Linux branches in prefs.py that previously had no tests
(font metrics, GPU spoofing, MSAA from profile, canvas noise mask
per renderer, WebGL extension preservation, Xvfb workarounds,
virtual_display no-op) and add construction smoke tests for
_LinuxVirtualDisplay. Also fix two host-platform-dependent tests
so the suite stays green on both Windows and Linux.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final sweep adds unit tests for the modules left at 0% direct coverage
after Phases 1-9:
- launcher._tz_env: 7 tests covering the IANA -> POSIX mapping
including the Phoenix / Honolulu no-DST regression cases
- launcher._humanize_max_seconds, _default_context_kwargs: 11 tests
on the constructor-side helpers (no browser launch)
- _headless.make_virtual_display dispatcher + _WindowsVirtualDesktop
init/teardown: 8 tests (Linux dispatch branch covered without
spawning Xvfb, since __init__ does no I/O)
- async_api.InvisiblePlaywright constructor parity with sync: 8 tests
guarding against drift between the two APIs
Suite: 230 -> 264 passing. Pyramid stays clean: 243 unit / 12
integration / 9 e2e.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>