diff --git a/README.md b/README.md index 09f1312..3e2d1d5 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ page.goto("https://example.com") ## What's New in v0.3.0 -- **Chromium 145** (Linux) — latest stable, 25 fingerprint patches (up from 16). macOS v145 coming soon +- **Chromium 145** — latest stable, 25 fingerprint patches (up from 16). All platforms - **9 new patches** — screen dimensions, device memory, audio, WebGL, and more - **SHA-256 checksum verification** — binary downloads are verified for integrity - **CDP hardening** — audited and patched known automation detection vectors @@ -175,7 +175,7 @@ All tests verified against live detection services. Last tested: Mar 2026 (Chrom CloakBrowser is a thin wrapper (Python + JavaScript) around a custom-built Chromium binary: 1. **You install** → `pip install cloakbrowser` or `npm install cloakbrowser` -2. **First launch** → binary auto-downloads for your platform (Linux x64: Chromium 145, macOS: Chromium 142) +2. **First launch** → binary auto-downloads for your platform (Chromium 145) 3. **Every launch** → Playwright or Puppeteer starts with our binary + stealth args 4. **You write code** → standard Playwright/Puppeteer API, nothing new to learn @@ -354,9 +354,9 @@ Every launch automatically generates a **unique fingerprint**. A random seed (10 ### Default Fingerprint -Every `launch()` call sets these automatically. Defaults are **platform-aware** — macOS runs as a native Mac browser, Linux spoofs Windows: +Every `launch()` call sets these automatically. Defaults are **platform-aware** — macOS runs as a native Mac browser, Linux and Windows use the Windows fingerprint profile: -| Flag | Linux Default | macOS Default | Controls | +| Flag | Linux/Windows Default | macOS Default | Controls | |------|--------------|---------------|----------| | `--fingerprint` | Random (10000–99999) | Random (10000–99999) | Master seed for canvas, WebGL, audio, fonts, client rects | | `--fingerprint-platform` | `windows` | `macos` | `navigator.platform`, User-Agent OS, GPU pool selection | @@ -428,16 +428,14 @@ browser = launch(args=[ | Platform | Chromium | Patches | Status | |---|---|---|---| | Linux x86_64 | 145 | 25 | ✅ Latest | -| macOS arm64 (Apple Silicon) | 142 | 16 | ✅ Available (v145 coming soon) | -| macOS x86_64 (Intel) | 142 | 16 | ✅ Available (v145 coming soon) | -| Windows | — | — | Planned | +| macOS arm64 (Apple Silicon) | 145 | 25 | ✅ Latest | +| macOS x86_64 (Intel) | 145 | 25 | ✅ Latest | +| Windows x86_64 | 145 | 25 | ✅ Latest | -The wrapper auto-downloads the correct binary for your platform. Linux gets Chromium 145 with all 25 patches. macOS currently runs Chromium 142 (16 patches) — the v145 macOS build is in progress. +The wrapper auto-downloads the correct binary for your platform. **macOS first launch:** The binary is ad-hoc signed. On first run, macOS Gatekeeper will block it. Right-click the app → **Open** → click **Open** in the dialog. This is only needed once. -**On Windows?** You can still use CloakBrowser via Docker or with your own Chromium binary by setting `CLOAKBROWSER_BINARY_PATH=/path/to/chrome`. - ## Examples **Python** — see [`examples/`](examples/): @@ -456,12 +454,11 @@ The wrapper auto-downloads the correct binary for your platform. Linux gets Chro | Feature | Status | |---------|--------| | Linux x64 — Chromium 145 (25 patches) | ✅ Released | -| macOS arm64/x64 — Chromium 142 (16 patches) | ✅ Released | -| macOS arm64/x64 — Chromium 145 | 🔨 In progress | +| macOS arm64/x64 — Chromium 145 (25 patches) | ✅ Released | +| Windows x64 — Chromium 145 (25 patches) | ✅ Released | | JavaScript/Puppeteer + Playwright support | ✅ Released | | Fingerprint rotation per session | ✅ Released | | Built-in proxy rotation | 📋 Planned | -| Windows support | 📋 Planned | ## Docker diff --git a/cloakbrowser/config.py b/cloakbrowser/config.py index 4c87d58..3553a75 100644 --- a/cloakbrowser/config.py +++ b/cloakbrowser/config.py @@ -11,8 +11,7 @@ from ._version import __version__ # --------------------------------------------------------------------------- # Chromium version shipped with this release. -# Different platforms may ship different versions (e.g. Linux gets v145 first, -# macOS stays on v142 until Mac builds are ready). +# Different platforms may ship different versions during transition periods. # CHROMIUM_VERSION is the latest across all platforms (for display/reference). # Use get_chromium_version() for the current platform's actual version. # --------------------------------------------------------------------------- @@ -22,6 +21,7 @@ PLATFORM_CHROMIUM_VERSIONS: dict[str, str] = { "linux-x64": "145.0.7632.109", "darwin-arm64": "145.0.7632.109", "darwin-x64": "145.0.7632.109", + "windows-x64": "145.0.7632.109", } # --------------------------------------------------------------------------- @@ -51,7 +51,7 @@ def get_default_stealth_args() -> list[str]: "--fingerprint-gpu-renderer=ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)", ] - # Linux: spoof as Windows + # Linux/Windows: Windows fingerprint profile return base + [ "--fingerprint-platform=windows", "--fingerprint-hardware-concurrency=8", @@ -79,6 +79,8 @@ SUPPORTED_PLATFORMS: dict[tuple[str, str], str] = { ("Linux", "aarch64"): "linux-arm64", ("Darwin", "arm64"): "darwin-arm64", ("Darwin", "x86_64"): "darwin-x64", + ("Windows", "AMD64"): "windows-x64", + ("Windows", "x86_64"): "windows-x64", } # Platforms with pre-built binaries available for download (derived from version map). @@ -132,6 +134,8 @@ def get_binary_path(version: str | None = None) -> Path: if platform.system() == "Darwin": # macOS: Chromium.app bundle return binary_dir / "Chromium.app" / "Contents" / "MacOS" / "Chromium" + elif platform.system() == "Windows": + return binary_dir / "chrome.exe" else: # Linux: flat binary return binary_dir / "chrome" @@ -150,9 +154,8 @@ def check_platform_available() -> None: available = ", ".join(sorted(AVAILABLE_PLATFORMS)) import sys sys.exit( - f"\n\033[1mCloakBrowser\033[0m — Pre-built binaries are currently only available for: {available}.\n" - f"Windows builds are coming soon.\n\n" - f"To use CloakBrowser now, run in Docker (see README) or set CLOAKBROWSER_BINARY_PATH." + f"\n\033[1mCloakBrowser\033[0m — Pre-built binaries are currently only available for: {available}.\n\n" + f"To use CloakBrowser now, set CLOAKBROWSER_BINARY_PATH to a local Chromium binary." ) diff --git a/cloakbrowser/download.py b/cloakbrowser/download.py index 27a2321..18aa82c 100644 --- a/cloakbrowser/download.py +++ b/cloakbrowser/download.py @@ -330,7 +330,9 @@ def _is_executable(path: Path) -> bool: def _make_executable(path: Path) -> None: - """Make a file executable (chmod +x).""" + """Make a file executable (chmod +x). Skipped on Windows (no-op / AV lock risk).""" + if platform.system() == "Windows": + return current = path.stat().st_mode path.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) diff --git a/js/README.md b/js/README.md index b34b0e7..4f64d85 100644 --- a/js/README.md +++ b/js/README.md @@ -176,9 +176,7 @@ const page = await browser.newPage(); | Linux x86_64 | ✅ Available | | macOS arm64 (Apple Silicon) | ✅ Available | | macOS x86_64 (Intel) | ✅ Available | -| Windows | Planned | - -**On Windows?** You can still use CloakBrowser via Docker or with your own Chromium binary by setting `CLOAKBROWSER_BINARY_PATH=/path/to/chrome`. +| Windows x86_64 | ✅ Available | ## Requirements diff --git a/js/src/config.ts b/js/src/config.ts index 67e7ca4..1dfa75e 100644 --- a/js/src/config.ts +++ b/js/src/config.ts @@ -23,8 +23,7 @@ export { WRAPPER_VERSION }; // --------------------------------------------------------------------------- // Chromium version shipped with this release. -// Different platforms may ship different versions (e.g. Linux gets v145 first, -// macOS stays on v142 until Mac builds are ready). +// Different platforms may ship different versions during transition periods. // CHROMIUM_VERSION is the latest across all platforms (for display/reference). // Use getChromiumVersion() for the current platform's actual version. // --------------------------------------------------------------------------- @@ -32,8 +31,9 @@ export const CHROMIUM_VERSION = "145.0.7632.109"; export const PLATFORM_CHROMIUM_VERSIONS: Record = { "linux-x64": "145.0.7632.109", - "darwin-arm64": "142.0.7444.175", - "darwin-x64": "142.0.7444.175", + "darwin-arm64": "145.0.7632.109", + "darwin-x64": "145.0.7632.109", + "windows-x64": "145.0.7632.109", }; // --------------------------------------------------------------------------- @@ -44,6 +44,7 @@ const SUPPORTED_PLATFORMS: Record = { "linux-arm64": "linux-arm64", "darwin-arm64": "darwin-arm64", "darwin-x64": "darwin-x64", + "win32-x64": "windows-x64", }; // Platforms with pre-built binaries available for download (derived from version map). @@ -64,6 +65,7 @@ export function getPlatformTag(): string { else if (platform === "linux" && arch === "arm64") key = "linux-arm64"; else if (platform === "darwin" && arch === "arm64") key = "darwin-arm64"; else if (platform === "darwin" && arch === "x64") key = "darwin-x64"; + else if (platform === "win32" && arch === "x64") key = "win32-x64"; else { const supported = Object.values(SUPPORTED_PLATFORMS).join(", "); throw new Error( @@ -92,6 +94,9 @@ export function getBinaryPath(version?: string): string { if (process.platform === "darwin") { return path.join(binaryDir, "Chromium.app", "Contents", "MacOS", "Chromium"); } + if (process.platform === "win32") { + return path.join(binaryDir, "chrome.exe"); + } return path.join(binaryDir, "chrome"); } @@ -102,9 +107,8 @@ export function checkPlatformAvailable(): void { if (!AVAILABLE_PLATFORMS.has(tag)) { const available = [...AVAILABLE_PLATFORMS].sort().join(", "); throw new Error( - `CloakBrowser — Pre-built binaries are currently only available for: ${available}.\n` + - `Windows builds are coming soon.\n\n` + - `To use CloakBrowser now, run in Docker (see README) or set CLOAKBROWSER_BINARY_PATH.` + `CloakBrowser — Pre-built binaries are currently only available for: ${available}.\n\n` + + `To use CloakBrowser now, set CLOAKBROWSER_BINARY_PATH to a local Chromium binary.` ); } } @@ -198,10 +202,15 @@ export function getDefaultStealthArgs(): string[] { if (isMac) { // macOS: run as native Mac browser — GPU/UA match natively - return [...base, "--fingerprint-platform=macos"]; + return [ + ...base, + "--fingerprint-platform=macos", + "--fingerprint-gpu-vendor=Google Inc. (Apple)", + "--fingerprint-gpu-renderer=ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)", + ]; } - // Linux: spoof as Windows + // Linux/Windows: Windows fingerprint profile return [ ...base, "--fingerprint-platform=windows", diff --git a/js/src/download.ts b/js/src/download.ts index 8311880..6219557 100644 --- a/js/src/download.ts +++ b/js/src/download.ts @@ -363,9 +363,9 @@ async function extractArchive( // Flatten single subdirectory if needed flattenSingleSubdir(destDir); - // Make binary executable + // Make binary executable (skip on Windows — no-op / AV lock risk) const bp = binaryPath || getBinaryPath(); - if (fs.existsSync(bp)) { + if (process.platform !== "win32" && fs.existsSync(bp)) { fs.chmodSync(bp, 0o755); } diff --git a/js/tests/launch.test.ts b/js/tests/launch.test.ts index df79e4b..4cd4dc1 100644 --- a/js/tests/launch.test.ts +++ b/js/tests/launch.test.ts @@ -7,7 +7,7 @@ describe("binaryInfo", () => { const info = binaryInfo(); expect(info.version).toBe(getChromiumVersion()); - expect(info.platform).toMatch(/^(linux|darwin)-(x64|arm64)$/); + expect(info.platform).toMatch(/^(linux|darwin|windows)-(x64|arm64)$/); expect(info.binaryPath).toBeTruthy(); expect(typeof info.installed).toBe("boolean"); expect(info.cacheDir).toContain("cloakbrowser"); diff --git a/js/tests/update.test.ts b/js/tests/update.test.ts index e06bc46..91069e7 100644 --- a/js/tests/update.test.ts +++ b/js/tests/update.test.ts @@ -100,7 +100,7 @@ describe("latest version (platform-aware)", () => { { tag_name: "chromium-v145.0.7718.0", draft: false, - assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64"]), + assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]), }, ]); expect(await getLatestChromiumVersion()).toBe("145.0.7718.0"); @@ -116,7 +116,7 @@ describe("latest version (platform-aware)", () => { { tag_name: "chromium-v142.0.7444.175", draft: false, - assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64"]), + assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]), }, ]); const result = await getLatestChromiumVersion(); @@ -133,14 +133,14 @@ describe("latest version (platform-aware)", () => { { tag_name: "chromium-v145.0.7718.0", draft: false, - assets: [{ name: "cloakbrowser-windows-x64.tar.gz" }], + assets: [{ name: "cloakbrowser-freebsd-x64.tar.gz" }], }, ]); expect(await getLatestChromiumVersion()).toBeNull(); }); it("skips draft releases", async () => { - const all = ["linux-x64", "darwin-arm64", "darwin-x64"]; + const all = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]; mockFetch([ { tag_name: "chromium-v999.0.0.0", draft: true, assets: makeAssets(all) }, { tag_name: "chromium-v145.0.7718.0", draft: false, assets: makeAssets(all) }, diff --git a/tests/test_update.py b/tests/test_update.py index fdaefdc..f1faa78 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -166,7 +166,7 @@ class TestGetLatestVersion: { "tag_name": "chromium-v145.0.7718.0", "draft": False, - "assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64"]), + "assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]), }, ] mock_response.raise_for_status = MagicMock() @@ -187,7 +187,7 @@ class TestGetLatestVersion: { "tag_name": "chromium-v142.0.7444.175", "draft": False, - "assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64"]), + "assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]), }, ] mock_response.raise_for_status = MagicMock() @@ -202,7 +202,7 @@ class TestGetLatestVersion: def test_skips_draft_releases(self): mock_response = MagicMock() - all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64"] + all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"] mock_response.json.return_value = [ {"tag_name": "chromium-v999.0.0.0", "draft": True, "assets": self._make_assets(all_platforms)}, {"tag_name": "chromium-v145.0.7718.0", "draft": False, "assets": self._make_assets(all_platforms)}, @@ -215,7 +215,7 @@ class TestGetLatestVersion: def test_skips_non_chromium_tags(self): mock_response = MagicMock() - all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64"] + all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"] mock_response.json.return_value = [ {"tag_name": "v0.2.0", "draft": False, "assets": self._make_assets(all_platforms)}, {"tag_name": "chromium-v145.0.7718.0", "draft": False, "assets": self._make_assets(all_platforms)}, @@ -233,7 +233,7 @@ class TestGetLatestVersion: { "tag_name": "chromium-v145.0.7718.0", "draft": False, - "assets": [{"name": "cloakbrowser-windows-x64.tar.gz"}], + "assets": [{"name": "cloakbrowser-freebsd-x64.tar.gz"}], }, ] mock_response.raise_for_status = MagicMock()