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".
This commit is contained in:
feder-cr
2026-05-12 21:34:14 -07:00
commit 7a983e99c5
51 changed files with 10967 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
import subprocess
import sys
def test_version_subcommand():
r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "version"],
capture_output=True, text=True, check=True,
)
assert "firefox-" in r.stdout
assert "invisible-playwright" in r.stdout.lower()
def test_help_subcommand():
r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "--help"],
capture_output=True, text=True,
)
assert r.returncode == 0
assert "fetch" in r.stdout
assert "path" in r.stdout
assert "clear-cache" in r.stdout
+29
View File
@@ -0,0 +1,29 @@
from invisible_playwright.constants import BINARY_VERSION, BINARY_BASENAME, ARCHIVE_NAME
def test_binary_version_format():
assert BINARY_VERSION.startswith("firefox-")
assert BINARY_VERSION.split("-", 1)[1].isdigit()
def test_archive_name_windows():
name = ARCHIVE_NAME("win32", "AMD64")
assert name.endswith(".zip")
assert "win-x86_64" in name
def test_archive_name_linux():
name = ARCHIVE_NAME("linux", "x86_64")
assert name.endswith(".tar.gz")
assert "linux-x86_64" in name
def test_archive_name_unsupported_raises():
import pytest
with pytest.raises(NotImplementedError):
ARCHIVE_NAME("darwin", "arm64")
def test_binary_basename_format():
assert "firefox" in BINARY_BASENAME.lower()
assert "stealth" in BINARY_BASENAME.lower()
+71
View File
@@ -0,0 +1,71 @@
import hashlib
from pathlib import Path
import pytest
import responses
from invisible_playwright.download import ensure_binary
from invisible_playwright.constants import BINARY_VERSION
def _make_zip(path: Path, inner_name: str, payload: bytes) -> bytes:
import io
import zipfile
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr(inner_name, payload)
data = buf.getvalue()
path.write_bytes(data)
return data
@responses.activate
def test_ensure_binary_downloads_and_verifies(tmp_path, monkeypatch):
"""Full path: cache miss -> HTTP GET -> SHA256 check -> extract -> return path."""
cache = tmp_path / "cache"
monkeypatch.setattr("invisible_playwright.download.cache_root", lambda: cache)
archive_path = tmp_path / "archive.zip"
archive_bytes = _make_zip(archive_path, "firefox.exe", b"PEX!")
archive_sha = hashlib.sha256(archive_bytes).hexdigest()
from invisible_playwright.constants import ARCHIVE_NAME
asset = ARCHIVE_NAME("win32", "AMD64")
url_archive = f"https://github.com/feder-cr/invisible_playwright/releases/download/{BINARY_VERSION}/{asset}"
url_sums = f"https://github.com/feder-cr/invisible_playwright/releases/download/{BINARY_VERSION}/checksums.txt"
responses.add(responses.GET, url_archive, body=archive_bytes, status=200,
content_type="application/zip")
responses.add(responses.GET, url_sums,
body=f"{archive_sha} {asset}\n", status=200)
monkeypatch.setattr("sys.platform", "win32")
import platform
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
path = ensure_binary()
assert Path(path).exists()
assert Path(path).name == "firefox.exe"
@responses.activate
def test_ensure_binary_rejects_sha_mismatch(tmp_path, monkeypatch):
cache = tmp_path / "cache"
monkeypatch.setattr("invisible_playwright.download.cache_root", lambda: cache)
archive_path = tmp_path / "archive.zip"
archive_bytes = _make_zip(archive_path, "firefox.exe", b"PEX!")
wrong_sha = "0" * 64
from invisible_playwright.constants import ARCHIVE_NAME
asset = ARCHIVE_NAME("win32", "AMD64")
url_archive = f"https://github.com/feder-cr/invisible_playwright/releases/download/{BINARY_VERSION}/{asset}"
url_sums = f"https://github.com/feder-cr/invisible_playwright/releases/download/{BINARY_VERSION}/checksums.txt"
responses.add(responses.GET, url_archive, body=archive_bytes, status=200)
responses.add(responses.GET, url_sums, body=f"{wrong_sha} {asset}\n", status=200)
monkeypatch.setattr("sys.platform", "win32")
import platform
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
with pytest.raises(RuntimeError, match="SHA256"):
ensure_binary()
+35
View File
@@ -0,0 +1,35 @@
from invisible_playwright._fpforge import generate_profile
from invisible_playwright.prefs import translate_profile_to_prefs
def test_translate_includes_gpu_renderer():
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)
assert prefs["zoom.stealth.webgl.renderer"] == p.gpu.renderer
assert prefs["zoom.stealth.webgl.vendor"] == p.gpu.vendor
def test_translate_includes_screen():
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)
assert prefs["zoom.stealth.screen.width"] == p.screen.width
assert prefs["zoom.stealth.screen.height"] == p.screen.height
def test_translate_is_deterministic_per_seed():
a = translate_profile_to_prefs(generate_profile(seed=42))
b = translate_profile_to_prefs(generate_profile(seed=42))
assert a == b
def test_translate_varies_across_seeds():
a = translate_profile_to_prefs(generate_profile(seed=1))
b = translate_profile_to_prefs(generate_profile(seed=2))
assert a != b
def test_translate_has_stealth_baseline_constants():
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)
assert prefs.get("privacy.resistFingerprinting") is False
assert "media.peerconnection.enabled" in prefs