mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
650ba36549 |
@@ -1,95 +0,0 @@
|
||||
# CloakBrowser Binary License
|
||||
|
||||
**Version 1.0 — February 2026**
|
||||
|
||||
Copyright (c) 2026 CloakHQ. All rights reserved.
|
||||
|
||||
This license applies to the compiled CloakBrowser Chromium binary ("Binary") distributed via GitHub Releases and cloakbrowser.dev. It does **not** apply to the wrapper source code in this repository, which is licensed under the [MIT License](LICENSE).
|
||||
|
||||
By downloading, installing, or using the Binary, you agree to be bound by the terms of this license.
|
||||
|
||||
## Intellectual Property
|
||||
|
||||
The Binary is built on Chromium, which is open-source software by The Chromium Authors under the BSD 3-Clause License, and incorporates components from the open-source ungoogled-chromium project. CloakHQ's build configuration, patches, and the Binary as a combined work are the proprietary property of CloakHQ. This license governs the Binary as distributed by CloakHQ — it does not restrict rights granted by upstream open-source licenses to their respective components.
|
||||
|
||||
## Grant of Use
|
||||
|
||||
You are granted a non-exclusive, non-transferable, royalty-free license to use the Binary for personal or commercial purposes. No fees are required.
|
||||
|
||||
## Restrictions
|
||||
|
||||
You may NOT:
|
||||
|
||||
1. **Redistribute** the Binary, in whole or in part, whether modified or unmodified
|
||||
2. **Resell, sublicense, or repackage** the Binary, or include it in any product or service distributed to third parties
|
||||
3. **Reverse engineer, decompile, or disassemble** the Binary, or attempt to derive source code from it, except to the extent permitted by applicable law
|
||||
4. **Modify** the Binary or create derivative works based on it
|
||||
5. **Remove or alter** any copyright notices, license files, or attribution included with the Binary
|
||||
|
||||
Listing CloakBrowser as a dependency in your project (e.g., in `requirements.txt`, `package.json`, or documentation) is not redistribution — end users download the Binary directly from official CloakHQ channels.
|
||||
|
||||
Internal caching or mirroring (including via artifact repositories such as Artifactory or Nexus) of unmodified Binaries that were originally obtained from official CloakHQ distribution channels is permitted solely for internal operational purposes within your organization. This permission does not allow public redistribution or distribution to third parties.
|
||||
|
||||
## Official Distribution
|
||||
|
||||
The Binary must originally be obtained from official CloakHQ distribution channels, including GitHub Releases (github.com/CloakHQ/CloakBrowser) and cloakbrowser.dev. Internal organizational mirrors permitted under the Restrictions section are not considered unauthorized sources.
|
||||
|
||||
## Trademark Notice
|
||||
|
||||
This license does not grant you any right to use the CloakHQ or CloakBrowser name, logo, or trademarks, except for nominative use reasonably necessary to refer to CloakHQ or CloakBrowser.
|
||||
|
||||
## Attribution
|
||||
|
||||
Attribution is appreciated but not required. If you'd like to credit CloakBrowser, a "Powered by CloakBrowser" notice with a link to https://github.com/CloakHQ/CloakBrowser in your documentation, README, or about page is welcome.
|
||||
|
||||
## Acceptable Use
|
||||
|
||||
You are solely responsible for how you use the Binary. You agree NOT to use the Binary for any activity that violates applicable laws or regulations in your jurisdiction. CloakHQ does not endorse, encourage, or support any illegal use.
|
||||
|
||||
## Indemnification
|
||||
|
||||
You agree to indemnify and hold harmless CloakHQ and its contributors from any claims, damages, losses, liabilities, and expenses (including reasonable legal fees) arising from your unlawful use of the Binary or your violation of this license.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
THE BINARY IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE BINARY OR THE USE OR OTHER DEALINGS IN THE BINARY.
|
||||
|
||||
## Data Collection
|
||||
|
||||
CloakHQ does not intentionally include telemetry, analytics, or tracking mechanisms in the Binary. The Binary is built on ungoogled-chromium, which removes Google-specific services and telemetry. Any network activity may result from normal browser operation, Chromium subsystems, user configuration, extensions, or the web pages and services you access, and not from any telemetry or analytics service operated by CloakHQ.
|
||||
|
||||
## Updates
|
||||
|
||||
CloakHQ is under no obligation to provide updates, patches, new versions, or support for the Binary. Updates, when provided, are subject to the terms of this license.
|
||||
|
||||
## Termination
|
||||
|
||||
This license terminates automatically if you violate any of its terms. Upon termination, you must destroy all copies of the Binary in your possession. The Intellectual Property, Restrictions, Trademark Notice, Indemnification, Disclaimer, Governing Law, Reservation of Rights, Entire Agreement, No Waiver, Assignment, and Severability sections survive termination.
|
||||
|
||||
## Governing Law
|
||||
|
||||
This license is governed by the laws of the jurisdiction in which CloakHQ is established. Any disputes arising under this license shall be subject to the exclusive jurisdiction of the courts in that jurisdiction.
|
||||
|
||||
## Reservation of Rights
|
||||
|
||||
All rights not expressly granted under this license are reserved by CloakHQ.
|
||||
|
||||
## Entire Agreement
|
||||
|
||||
This license constitutes the entire agreement between you and CloakHQ regarding the Binary and supersedes any prior or contemporaneous understandings relating to the Binary.
|
||||
|
||||
## No Waiver
|
||||
|
||||
Failure by CloakHQ to enforce any provision of this license does not constitute a waiver of that provision or any other provision.
|
||||
|
||||
## Assignment
|
||||
|
||||
You may not assign or transfer this license or any rights under it without prior written consent from CloakHQ.
|
||||
|
||||
## Severability
|
||||
|
||||
If any provision of this license is held to be unenforceable or invalid, that provision shall be modified to the minimum extent necessary to make it enforceable, and all remaining provisions shall continue in full force and effect.
|
||||
|
||||
## Contact
|
||||
|
||||
For licensing inquiries, including redistribution or OEM licensing, contact cloakhq@pm.me.
|
||||
+2
-36
@@ -6,41 +6,9 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
|
||||
---
|
||||
|
||||
## [0.3.4] — 2026-03-04
|
||||
## [0.3.0] — Unreleased
|
||||
|
||||
Binary v14: auto-spoof restored with seed, wrapper simplified to match.
|
||||
|
||||
- **[binary]** Restore full auto-spoof when `--fingerprint=seed` is set — all randomized properties now derive from the seed consistently
|
||||
- **[binary]** Auto-inject random fingerprint seed at startup if none provided. Binary is stealthy with zero flags
|
||||
- **[binary]** 26 source-level C++ patches (up from 25)
|
||||
- **[wrapper]** Simplify default stealth args — remove flags the binary now auto-generates. Wrapper still sets platform profile on Linux and `--no-sandbox`
|
||||
- **[wrapper]** Fix timezone in `launch_context()` — use Playwright's per-context timezone instead of binary flag, fixing mismatch when creating new browser contexts with geoip
|
||||
- **[wrapper]** Clarify README platform detection behavior
|
||||
|
||||
## [0.3.3] — 2026-03-03
|
||||
|
||||
All platforms now run Chromium 145 v2 with 25 patches. Windows x64 added.
|
||||
|
||||
- **[binary]** Auto-spoof by default — binary is stealthy with zero flags. Random fingerprint seed auto-generated at startup, no wrapper or configuration required
|
||||
- **[binary]** Platform-aware auto-detection — GPU, screen dimensions, and User-Agent automatically match the real OS (macOS, Linux, Windows) without explicit flags
|
||||
- **[binary]** Expanded GPU model database for realistic per-session diversity
|
||||
- **[binary]** First macOS v145 builds (arm64 + x64) — 25 patches, up from 16 on v142
|
||||
- **[binary]** First Windows x64 v145 build — 25 patches
|
||||
- **[wrapper]** Add Windows x64 platform support — auto-download, binary path resolution, and platform detection
|
||||
- **[wrapper]** Upgrade macOS (arm64 + x64) from Chromium 142 to 145 — all platforms now ship the same 25-patch build
|
||||
- **[wrapper]** Add explicit Mac GPU flags (`Apple M3 Metal` renderer) to default stealth args for consistent WebGL fingerprints
|
||||
- **[wrapper]** Improve reCAPTCHA stealth test — wait for score element instead of blind sleep
|
||||
- **[wrapper]** JS: add `win32-x64` platform mapping, Windows binary path (`chrome.exe`)
|
||||
|
||||
## [0.3.1] — 2026-03-03
|
||||
|
||||
- **[wrapper]** Auto-check for wrapper updates on startup (PyPI/npm). Notifies users when a newer wrapper version is available. Runs once per process, respects `CLOAKBROWSER_AUTO_UPDATE=false`.
|
||||
|
||||
---
|
||||
|
||||
## [0.3.0] — 2026-03-02
|
||||
|
||||
Chromium v145 upgrade. 25 fingerprint patches (up from 16). New download verification and fallback system. macOS v145 binary builds pending.
|
||||
Chromium v145 upgrade. 25 fingerprint patches (up from 16). New download verification and fallback system. Pending: macOS v145 binary builds.
|
||||
|
||||
### Breaking
|
||||
|
||||
@@ -54,8 +22,6 @@ Chromium v145 upgrade. 25 fingerprint patches (up from 16). New download verific
|
||||
- **[binary]** Stability and performance improvements across fingerprint patches
|
||||
- **[binary]** New optional flags: `--fingerprint-fonts-dir`, `--fingerprint-taskbar-height`
|
||||
- **[wrapper]** Sync wrapper with latest binary changes: updated flag names, viewport, and defaults
|
||||
- **[wrapper]** Per-platform Chromium versioning — Linux and macOS can track different binary versions independently
|
||||
- **[wrapper]** Improved SHA-256 checksum verification and version marker migration
|
||||
|
||||
### 2026-03-01
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 CloakHQ
|
||||
Copyright (c) 2026 cloakbrowser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -35,7 +35,7 @@ Drop-in Playwright/Puppeteer replacement for Python and JavaScript.<br>
|
||||
Same API, same code — just swap the import. <strong>3 lines of code, 30 seconds to unblock.</strong>
|
||||
</p>
|
||||
|
||||
- 🔒 **26 source-level C++ patches** — not JS injection, not config flags
|
||||
- 🔒 **25 source-level C++ patches** — not JS injection, not config flags
|
||||
- 🛡️ **CDP stealth built-in** — uses [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright) to reduce Playwright's automation footprint
|
||||
- 🎯 **0.9 reCAPTCHA v3 score** — human-level, server-verified
|
||||
- ☁️ **Passes Cloudflare Turnstile**, FingerprintJS, BrowserScan — 30/30 tests
|
||||
@@ -104,16 +104,14 @@ page.goto("https://example.com")
|
||||
|
||||
> ⭐ **Star** to show support — **[Watch releases](https://github.com/CloakHQ/CloakBrowser/subscription)** to get notified when new builds drop.
|
||||
|
||||
## What's New in v0.3.4
|
||||
## What's New in v0.3.0
|
||||
|
||||
- **All 4 platforms** — Linux x64, macOS arm64, macOS x64, and Windows x64 all on Chromium 145
|
||||
- **26 fingerprint patches** — 10 new patches since v142 (screen, device memory, audio, WebGL, auto-spoof, and more)
|
||||
- **Stealthy with zero flags** — binary auto-generates a random fingerprint seed at startup. No configuration required
|
||||
- **Deterministic seeds** — `--fingerprint=seed` produces the same identity across launches for session persistence
|
||||
- **Full stealth audit** — every patch reviewed for detection vectors, multiple fixes shipped
|
||||
- **Timezone & locale from proxy IP** — `launch(proxy="...", geoip=True)` auto-detects timezone and locale
|
||||
- **Chromium 145** (Linux) — latest stable, 25 fingerprint patches (up from 16). macOS v145 coming soon
|
||||
- **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
|
||||
- **Full stealth audit** — every patch reviewed for detection vectors, multiple fixes shipped
|
||||
- **Timezone & locale from proxy IP** — `launch(proxy="...", geoip=True)` auto-detects timezone and locale
|
||||
|
||||
See the full [CHANGELOG.md](CHANGELOG.md) for details.
|
||||
|
||||
@@ -177,11 +175,11 @@ 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 (Chromium 145)
|
||||
2. **First launch** → binary auto-downloads for your platform (Linux x64: Chromium 145, macOS: Chromium 142)
|
||||
3. **Every launch** → Playwright or Puppeteer starts with our binary + stealth args
|
||||
4. **You write code** → standard Playwright/Puppeteer API, nothing new to learn
|
||||
|
||||
The binary includes 26 source-level patches covering canvas, WebGL, audio, fonts, GPU, screen properties, hardware reporting, and automation signal removal.
|
||||
The binary includes 25 source-level patches covering canvas, WebGL, audio, fonts, GPU, screen properties, hardware reporting, and automation signal removal.
|
||||
|
||||
These are compiled into the Chromium binary — not injected via JavaScript, not set via flags.
|
||||
|
||||
@@ -204,7 +202,7 @@ browser = launch(headless=False)
|
||||
browser = launch(proxy="http://user:pass@proxy:8080")
|
||||
|
||||
# With extra Chrome args
|
||||
browser = launch(args=["--disable-gpu"])
|
||||
browser = launch(args=["--disable-gpu", "--window-size=1920,1080"])
|
||||
|
||||
# With timezone and locale (sets both binary flags and Playwright context)
|
||||
browser = launch(timezone="America/New_York", locale="en-US")
|
||||
@@ -285,7 +283,7 @@ const browser = await launch();
|
||||
const browser = await launch({
|
||||
headless: false,
|
||||
proxy: 'http://user:pass@proxy:8080',
|
||||
args: ['--fingerprint=12345'],
|
||||
args: ['--window-size=1920,1080'],
|
||||
timezone: 'America/New_York',
|
||||
locale: 'en-US',
|
||||
});
|
||||
@@ -344,17 +342,7 @@ clearCache();
|
||||
|
||||
## Fingerprint Management
|
||||
|
||||
The binary is **stealthy by default** — no flags needed. It auto-generates a random fingerprint seed at startup and spoofs all detectable values (GPU, hardware specs, screen dimensions, canvas, WebGL, audio, fonts). Every launch produces a fresh, coherent identity.
|
||||
|
||||
**How fingerprinting works:**
|
||||
|
||||
| Scenario | What happens |
|
||||
|----------|-------------|
|
||||
| **No flags** | Random seed auto-generated at startup. GPU, screen, hardware specs, and all noise patches are spoofed automatically. Fresh identity each launch. |
|
||||
| **`--fingerprint=seed`** | Deterministic identity from the seed. Same seed = same fingerprint across launches. Use this for session persistence (returning visitor). |
|
||||
| **`--fingerprint=seed` + explicit flags** | Explicit flags override individual auto-generated values. The seed fills in everything else. |
|
||||
|
||||
The binary detects its platform at compile time — a macOS binary reports as macOS with Apple GPU, a Linux binary reports as Linux with NVIDIA GPU. The **wrapper** overrides this on Linux by passing `--fingerprint-platform=windows`, so sessions appear as Windows desktops (more common fingerprint, harder to cluster). Use `--fingerprint-platform` for cross-platform spoofing when running the binary directly.
|
||||
Every launch automatically generates a **unique fingerprint**. A random seed (10000–99999) drives all seed-based patches — canvas, WebGL, audio, fonts, and client rects all produce consistent, correlated values derived from that single seed.
|
||||
|
||||
> **Tip: Use a fixed seed when revisiting the same site.** A random seed makes every session look like a different device — which can be suspicious when hitting the same site repeatedly from the same IP. For reCAPTCHA v3 Enterprise and similar scoring systems, a fixed seed produces a consistent fingerprint across sessions, making you look like a returning visitor:
|
||||
> ```python
|
||||
@@ -366,20 +354,21 @@ The binary detects its platform at compile time — a macOS binary reports as ma
|
||||
|
||||
### Default Fingerprint
|
||||
|
||||
Every `launch()` call sets these automatically. The **wrapper** applies platform-aware defaults — on Linux it spoofs as Windows for a more common fingerprint, on macOS it runs as a native Mac browser:
|
||||
Every `launch()` call sets these automatically. Defaults are **platform-aware** — macOS runs as a native Mac browser, Linux spoofs Windows:
|
||||
|
||||
| Flag | Linux/Windows Default | macOS Default | Controls |
|
||||
| Flag | Linux 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 |
|
||||
| `--fingerprint-gpu-vendor` | `NVIDIA Corporation` | `Google Inc. (Apple)` | WebGL `UNMASKED_VENDOR_WEBGL` |
|
||||
| `--fingerprint-gpu-renderer` | `NVIDIA GeForce RTX 3070` | `ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)` | WebGL `UNMASKED_RENDERER_WEBGL` |
|
||||
| `--fingerprint-hardware-concurrency` | `8` | *(not set — uses real value)* | `navigator.hardwareConcurrency` |
|
||||
| `--fingerprint-gpu-vendor` | `NVIDIA Corporation` | *(not set — native Apple GPU)* | WebGL `UNMASKED_VENDOR_WEBGL` |
|
||||
| `--fingerprint-gpu-renderer` | `NVIDIA GeForce RTX 3070` | *(not set — native Metal renderer)* | WebGL `UNMASKED_RENDERER_WEBGL` |
|
||||
| `--fingerprint-device-memory` | `8` | *(not set)* | `navigator.deviceMemory` |
|
||||
| `--fingerprint-screen-width` | `1920` | *(not set)* | Screen width reporting |
|
||||
| `--fingerprint-screen-height` | `1080` | *(not set)* | Screen height reporting |
|
||||
| `--window-size` | `1920,1080` | *(not set)* | Browser window dimensions |
|
||||
|
||||
The binary auto-generates hardware concurrency (8), device memory (8), and screen dimensions (1920x1080 on Windows/Linux, 1440x900 on macOS) from the seed. Override with explicit flags if needed.
|
||||
|
||||
> **Using the binary directly?** It works out of the box with zero flags — the binary auto-spoofs everything. Pass `--fingerprint=seed` for a persistent identity, or use explicit flags like `--fingerprint-gpu-renderer` to override any auto-generated value.
|
||||
|
||||
> **Production tip:** For better stealth at scale, pass your own GPU, screen, and hardware values instead of relying on defaults. Custom parameters make your sessions harder to cluster by anti-bot systems that look for uniform fingerprint profiles.
|
||||
> **Important:** `--fingerprint-platform` should always be set. Without it, platform-specific patches (GPU, UA, screen, taskbar) won't activate. The wrapper handles this automatically.
|
||||
|
||||
### Additional Flags
|
||||
|
||||
@@ -387,10 +376,6 @@ Supported by the binary but **not set by default** — pass via `args` to custom
|
||||
|
||||
| Flag | Controls |
|
||||
|------|----------|
|
||||
| `--fingerprint-hardware-concurrency` | `navigator.hardwareConcurrency` (auto-generated: `8`) |
|
||||
| `--fingerprint-device-memory` | `navigator.deviceMemory` in GB (auto-generated: `8`) |
|
||||
| `--fingerprint-screen-width` | Screen width (auto-generated: `1920` Win/Linux, `1440` macOS) |
|
||||
| `--fingerprint-screen-height` | Screen height (auto-generated: `1080` Win/Linux, `900` macOS) |
|
||||
| `--fingerprint-brand` | Browser brand: `Chrome`, `Edge`, `Opera`, `Vivaldi` |
|
||||
| `--fingerprint-brand-version` | Brand version (UA + Client Hints) |
|
||||
| `--fingerprint-platform-version` | Client Hints platform version |
|
||||
@@ -412,6 +397,7 @@ browser = launch(args=["--fingerprint=42069"])
|
||||
browser = launch(stealth_args=False, args=[
|
||||
"--fingerprint=42069",
|
||||
"--fingerprint-platform=windows",
|
||||
"--fingerprint-hardware-concurrency=8",
|
||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 3070",
|
||||
])
|
||||
@@ -439,15 +425,17 @@ browser = launch(args=[
|
||||
|
||||
| Platform | Chromium | Patches | Status |
|
||||
|---|---|---|---|
|
||||
| Linux x86_64 | 145 | 26 | ✅ Latest |
|
||||
| macOS arm64 (Apple Silicon) | 145 | 26 | ✅ Latest |
|
||||
| macOS x86_64 (Intel) | 145 | 26 | ✅ Latest |
|
||||
| Windows x86_64 | 145 | 26 | ✅ Latest |
|
||||
| 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 |
|
||||
|
||||
The wrapper auto-downloads the correct binary for your platform.
|
||||
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.
|
||||
|
||||
**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/):
|
||||
@@ -465,12 +453,13 @@ The wrapper auto-downloads the correct binary for your platform.
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Linux x64 — Chromium 145 (26 patches) | ✅ Released |
|
||||
| macOS arm64/x64 — Chromium 145 (26 patches) | ✅ Released |
|
||||
| Windows x64 — Chromium 145 (26 patches) | ✅ Released |
|
||||
| Linux x64 — Chromium 145 (25 patches) | ✅ Released |
|
||||
| macOS arm64/x64 — Chromium 142 (16 patches) | ✅ Released |
|
||||
| macOS arm64/x64 — Chromium 145 | 🔨 In progress |
|
||||
| JavaScript/Puppeteer + Playwright support | ✅ Released |
|
||||
| Fingerprint rotation per session | ✅ Released |
|
||||
| Built-in proxy rotation | 📋 Planned |
|
||||
| Windows support | 📋 Planned |
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -554,13 +543,6 @@ const browser = await launch({ args: ['--disable-http2'] });
|
||||
|
||||
Only use this flag for sites that require it — most sites work fine with HTTP/2.
|
||||
|
||||
**Something not working? Make sure you're on the latest wrapper**
|
||||
Older versions may use outdated stealth args or download an older binary:
|
||||
```bash
|
||||
pip install -U cloakbrowser # Python
|
||||
npm install cloakbrowser@latest # JavaScript
|
||||
```
|
||||
|
||||
**Binary download fails / timeout**
|
||||
Set a custom download URL or use a local binary:
|
||||
```bash
|
||||
@@ -637,8 +619,7 @@ A: Yes. Pass `proxy="http://user:pass@host:port"` to `launch()`.
|
||||
|
||||
## License
|
||||
|
||||
- **Wrapper code** (this repository) — MIT. See [LICENSE](https://github.com/CloakHQ/CloakBrowser/blob/main/LICENSE).
|
||||
- **CloakBrowser binary** (compiled Chromium) — free to use, no redistribution. See [BINARY-LICENSE.md](https://github.com/CloakHQ/CloakBrowser/blob/main/BINARY-LICENSE.md).
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.4"
|
||||
__version__ = "0.3.0"
|
||||
|
||||
@@ -198,11 +198,8 @@ def launch_context(
|
||||
# Resolve geoip BEFORE launch() to avoid double-resolution and ensure
|
||||
# resolved values flow to both binary flags AND context params
|
||||
timezone_id, locale = _maybe_resolve_geoip(geoip, proxy, timezone_id, locale)
|
||||
# Skip --fingerprint-timezone binary flag: it only applies to the default
|
||||
# context and interferes with Playwright's timezone_id on new contexts.
|
||||
# Timezone is set via browser.new_context(timezone_id=...) below instead.
|
||||
browser = launch(headless=headless, proxy=proxy, args=args, stealth_args=stealth_args,
|
||||
timezone=None, locale=locale)
|
||||
timezone=timezone_id, locale=locale)
|
||||
|
||||
context_kwargs: dict[str, Any] = {}
|
||||
if user_agent:
|
||||
|
||||
+13
-15
@@ -11,7 +11,8 @@ from ._version import __version__
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Chromium version shipped with this release.
|
||||
# Different platforms may ship different versions during transition periods.
|
||||
# Different platforms may ship different versions (e.g. Linux gets v145 first,
|
||||
# macOS stays on v142 until Mac builds are ready).
|
||||
# CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||
# Use get_chromium_version() for the current platform's actual version.
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -19,9 +20,8 @@ CHROMIUM_VERSION = "145.0.7632.109"
|
||||
|
||||
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",
|
||||
"darwin-arm64": "142.0.7444.175",
|
||||
"darwin-x64": "142.0.7444.175",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -47,17 +47,18 @@ def get_default_stealth_args() -> list[str]:
|
||||
# Tell the fingerprint patches we're on macOS so GPU/UA match natively
|
||||
return base + [
|
||||
"--fingerprint-platform=macos",
|
||||
"--fingerprint-gpu-vendor=Google Inc. (Apple)",
|
||||
"--fingerprint-gpu-renderer=ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)",
|
||||
]
|
||||
|
||||
# Linux/Windows: Windows fingerprint profile
|
||||
# Hardware concurrency, device memory, screen, and window size are
|
||||
# auto-generated by the binary from the seed (v14+).
|
||||
# Linux: spoof as Windows
|
||||
return base + [
|
||||
"--fingerprint-platform=windows",
|
||||
"--fingerprint-hardware-concurrency=8",
|
||||
"--fingerprint-device-memory=8",
|
||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 3070",
|
||||
"--fingerprint-screen-width=1920",
|
||||
"--fingerprint-screen-height=1080",
|
||||
"--window-size=1920,1080",
|
||||
]
|
||||
|
||||
|
||||
@@ -76,8 +77,6 @@ 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).
|
||||
@@ -131,8 +130,6 @@ 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"
|
||||
@@ -151,8 +148,9 @@ 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\n"
|
||||
f"To use CloakBrowser now, set CLOAKBROWSER_BINARY_PATH to a local Chromium binary."
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from ._version import __version__ as _wrapper_version
|
||||
from .config import (
|
||||
CHROMIUM_VERSION,
|
||||
DOWNLOAD_BASE_URL,
|
||||
@@ -330,9 +329,7 @@ def _is_executable(path: Path) -> bool:
|
||||
|
||||
|
||||
def _make_executable(path: Path) -> None:
|
||||
"""Make a file executable (chmod +x). Skipped on Windows (no-op / AV lock risk)."""
|
||||
if platform.system() == "Windows":
|
||||
return
|
||||
"""Make a file executable (chmod +x)."""
|
||||
current = path.stat().st_mode
|
||||
path.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
@@ -459,37 +456,6 @@ def _write_version_marker(version: str) -> None:
|
||||
tmp.rename(marker)
|
||||
|
||||
|
||||
_wrapper_update_checked = False
|
||||
|
||||
|
||||
def _check_wrapper_update() -> None:
|
||||
"""Check PyPI for a newer wrapper version. Runs once per process."""
|
||||
global _wrapper_update_checked
|
||||
if _wrapper_update_checked:
|
||||
return
|
||||
_wrapper_update_checked = True
|
||||
if os.environ.get("CLOAKBROWSER_AUTO_UPDATE", "").lower() == "false":
|
||||
return
|
||||
if os.environ.get("CLOAKBROWSER_DOWNLOAD_URL"):
|
||||
return
|
||||
try:
|
||||
resp = httpx.get(
|
||||
"https://pypi.org/pypi/cloakbrowser/json",
|
||||
timeout=5.0,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
latest = resp.json()["info"]["version"]
|
||||
if _version_newer(latest, _wrapper_version):
|
||||
logger.warning(
|
||||
"Update available: cloakbrowser %s → %s. "
|
||||
"Run: pip install --upgrade cloakbrowser",
|
||||
_wrapper_version,
|
||||
latest,
|
||||
)
|
||||
except Exception:
|
||||
logger.debug("Wrapper update check failed", exc_info=True)
|
||||
|
||||
|
||||
def _check_and_download_update() -> None:
|
||||
"""Background task: check for newer binary, download if available."""
|
||||
try:
|
||||
@@ -527,12 +493,6 @@ def _check_and_download_update() -> None:
|
||||
|
||||
def _maybe_trigger_update_check() -> None:
|
||||
"""Fire-and-forget update check in a daemon thread."""
|
||||
# Wrapper update: once per process, not rate-limited
|
||||
if not _wrapper_update_checked:
|
||||
t = threading.Thread(target=_check_wrapper_update, daemon=True)
|
||||
t.start()
|
||||
|
||||
# Binary update: rate-limited to once per hour
|
||||
if not _should_check_for_update():
|
||||
return
|
||||
t = threading.Thread(target=_check_and_download_update, daemon=True)
|
||||
|
||||
@@ -143,15 +143,11 @@ def test_recaptcha(page):
|
||||
"""recaptcha-demo.appspot.com — Google's official reCAPTCHA v3 score."""
|
||||
page.goto(
|
||||
"https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
|
||||
wait_until="domcontentloaded",
|
||||
wait_until="networkidle",
|
||||
timeout=30000,
|
||||
)
|
||||
# Wait for backend response (step3 element appears when score arrives)
|
||||
try:
|
||||
page.wait_for_selector("li.step3", timeout=20000)
|
||||
time.sleep(1)
|
||||
except Exception:
|
||||
time.sleep(10) # fallback
|
||||
# Page auto-submits via grecaptcha.execute() — wait for backend response
|
||||
time.sleep(8)
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
|
||||
+6
-5
@@ -11,7 +11,7 @@
|
||||
|
||||
Drop-in Playwright/Puppeteer replacement. Same API — just swap the import. Scores **0.9 on reCAPTCHA v3**, passes **Cloudflare Turnstile**, and clears **30/30** stealth detection tests.
|
||||
|
||||
- 🔒 **26 source-level C++ patches** — not JS injection, not config flags
|
||||
- 🔒 **25 source-level C++ patches** — not JS injection, not config flags
|
||||
- 🎯 **0.9 reCAPTCHA v3 score** — human-level, server-verified
|
||||
- ☁️ **Passes Cloudflare Turnstile**, FingerprintJS, BrowserScan — 30/30 tests
|
||||
- 🔄 **Drop-in replacement** — works with both Playwright and Puppeteer
|
||||
@@ -72,7 +72,7 @@ const browser = await launch({ headless: false });
|
||||
|
||||
// Extra Chrome args
|
||||
const browser = await launch({
|
||||
args: ['--fingerprint=12345'],
|
||||
args: ['--window-size=1920,1080'],
|
||||
});
|
||||
|
||||
// With timezone and locale (sets --fingerprint-timezone and --lang binary flags)
|
||||
@@ -176,7 +176,9 @@ const page = await browser.newPage();
|
||||
| Linux x86_64 | ✅ Available |
|
||||
| macOS arm64 (Apple Silicon) | ✅ Available |
|
||||
| macOS x86_64 (Intel) | ✅ Available |
|
||||
| Windows x86_64 | ✅ 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`.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -215,5 +217,4 @@ Other tips for maximizing reCAPTCHA scores:
|
||||
|
||||
## License
|
||||
|
||||
- **Wrapper code** (this repository) — MIT. See [LICENSE](https://github.com/CloakHQ/CloakBrowser/blob/main/LICENSE).
|
||||
- **CloakBrowser binary** (compiled Chromium) — free to use, no redistribution. See [BINARY-LICENSE.md](https://github.com/CloakHQ/CloakBrowser/blob/main/BINARY-LICENSE.md).
|
||||
MIT — see [LICENSE](https://github.com/CloakHQ/CloakBrowser/blob/main/LICENSE).
|
||||
|
||||
+2
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.0",
|
||||
"description": "Stealth Chromium that passes every bot detection test. Drop-in Playwright/Puppeteer replacement with source-level fingerprint patches.",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -25,20 +25,12 @@
|
||||
"playwright",
|
||||
"puppeteer",
|
||||
"scraping",
|
||||
"web-scraping",
|
||||
"anti-detect",
|
||||
"antidetect",
|
||||
"undetected",
|
||||
"bot-detection",
|
||||
"fingerprint",
|
||||
"recaptcha",
|
||||
"cloudflare",
|
||||
"turnstile",
|
||||
"datadome",
|
||||
"captcha",
|
||||
"headless",
|
||||
"automation",
|
||||
"ai-agent"
|
||||
"datadome"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
+14
-34
@@ -6,24 +6,11 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Read wrapper version from package.json (single source of truth)
|
||||
let WRAPPER_VERSION = "0.0.0";
|
||||
try {
|
||||
const _configDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const _pkgPath = path.resolve(_configDir, "..", "package.json");
|
||||
const _pkg = JSON.parse(fs.readFileSync(_pkgPath, "utf-8")) as { version: string };
|
||||
WRAPPER_VERSION = _pkg.version;
|
||||
} catch {
|
||||
// Fallback — package.json not found (bundled or unusual layout).
|
||||
// Wrapper update check will compare against 0.0.0 and always suggest updating.
|
||||
}
|
||||
export { WRAPPER_VERSION };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Chromium version shipped with this release.
|
||||
// Different platforms may ship different versions during transition periods.
|
||||
// Different platforms may ship different versions (e.g. Linux gets v145 first,
|
||||
// macOS stays on v142 until Mac builds are ready).
|
||||
// CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||
// Use getChromiumVersion() for the current platform's actual version.
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -31,9 +18,8 @@ export const CHROMIUM_VERSION = "145.0.7632.109";
|
||||
|
||||
export const PLATFORM_CHROMIUM_VERSIONS: Record<string, string> = {
|
||||
"linux-x64": "145.0.7632.109",
|
||||
"darwin-arm64": "145.0.7632.109",
|
||||
"darwin-x64": "145.0.7632.109",
|
||||
"windows-x64": "145.0.7632.109",
|
||||
"darwin-arm64": "142.0.7444.175",
|
||||
"darwin-x64": "142.0.7444.175",
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -44,7 +30,6 @@ const SUPPORTED_PLATFORMS: Record<string, string> = {
|
||||
"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).
|
||||
@@ -65,7 +50,6 @@ 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(
|
||||
@@ -94,9 +78,6 @@ 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");
|
||||
}
|
||||
|
||||
@@ -107,8 +88,9 @@ 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\n` +
|
||||
`To use CloakBrowser now, set CLOAKBROWSER_BINARY_PATH to a local Chromium binary.`
|
||||
`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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -202,21 +184,19 @@ export function getDefaultStealthArgs(): string[] {
|
||||
|
||||
if (isMac) {
|
||||
// macOS: run as native Mac browser — GPU/UA match natively
|
||||
return [
|
||||
...base,
|
||||
"--fingerprint-platform=macos",
|
||||
"--fingerprint-gpu-vendor=Google Inc. (Apple)",
|
||||
"--fingerprint-gpu-renderer=ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)",
|
||||
];
|
||||
return [...base, "--fingerprint-platform=macos"];
|
||||
}
|
||||
|
||||
// Linux/Windows: spoof as Windows desktop
|
||||
// Hardware concurrency, device memory, screen, and window size are
|
||||
// auto-generated by the binary from the seed (v14+).
|
||||
// Linux: spoof as Windows
|
||||
return [
|
||||
...base,
|
||||
"--fingerprint-platform=windows",
|
||||
"--fingerprint-hardware-concurrency=8",
|
||||
"--fingerprint-device-memory=8",
|
||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 3070",
|
||||
"--fingerprint-screen-width=1920",
|
||||
"--fingerprint-screen-height=1080",
|
||||
"--window-size=1920,1080",
|
||||
];
|
||||
}
|
||||
|
||||
+3
-39
@@ -17,7 +17,6 @@ import {
|
||||
DOWNLOAD_BASE_URL,
|
||||
GITHUB_API_URL,
|
||||
GITHUB_DOWNLOAD_BASE_URL,
|
||||
WRAPPER_VERSION,
|
||||
checkPlatformAvailable,
|
||||
getBinaryDir,
|
||||
getBinaryPath,
|
||||
@@ -363,9 +362,9 @@ async function extractArchive(
|
||||
// Flatten single subdirectory if needed
|
||||
flattenSingleSubdir(destDir);
|
||||
|
||||
// Make binary executable (skip on Windows — no-op / AV lock risk)
|
||||
// Make binary executable
|
||||
const bp = binaryPath || getBinaryPath();
|
||||
if (process.platform !== "win32" && fs.existsSync(bp)) {
|
||||
if (fs.existsSync(bp)) {
|
||||
fs.chmodSync(bp, 0o755);
|
||||
}
|
||||
|
||||
@@ -478,36 +477,6 @@ function writeVersionMarker(version: string): void {
|
||||
fs.renameSync(tmp, marker);
|
||||
}
|
||||
|
||||
let wrapperUpdateChecked = false;
|
||||
|
||||
/** @internal Exported for testing only. */
|
||||
export function resetWrapperUpdateChecked(): void {
|
||||
wrapperUpdateChecked = false;
|
||||
}
|
||||
|
||||
/** @internal Exported for testing only. */
|
||||
export async function checkWrapperUpdate(): Promise<void> {
|
||||
if (wrapperUpdateChecked) return;
|
||||
wrapperUpdateChecked = true;
|
||||
if (process.env.CLOAKBROWSER_AUTO_UPDATE?.toLowerCase() === "false") return;
|
||||
if (process.env.CLOAKBROWSER_DOWNLOAD_URL) return;
|
||||
try {
|
||||
const resp = await fetch("https://registry.npmjs.org/cloakbrowser/latest", {
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
});
|
||||
if (!resp.ok) return;
|
||||
const data = (await resp.json()) as { version: string };
|
||||
if (data.version && versionNewer(data.version, WRAPPER_VERSION)) {
|
||||
console.warn(
|
||||
`[cloakbrowser] Update available: ${WRAPPER_VERSION} → ${data.version}. ` +
|
||||
`Run: npm install cloakbrowser@latest`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal — never block binary update check
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAndDownloadUpdate(): Promise<void> {
|
||||
try {
|
||||
// Record check timestamp first (rate limiting)
|
||||
@@ -545,12 +514,7 @@ async function checkAndDownloadUpdate(): Promise<void> {
|
||||
}
|
||||
|
||||
function maybeTriggerUpdateCheck(): void {
|
||||
// Wrapper update: once per process, not rate-limited
|
||||
if (!wrapperUpdateChecked) {
|
||||
checkWrapperUpdate().catch(() => {});
|
||||
}
|
||||
|
||||
// Binary update: rate-limited to once per hour
|
||||
if (!shouldCheckForUpdate()) return;
|
||||
// Fire-and-forget — don't await
|
||||
checkAndDownloadUpdate().catch(() => {});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("binaryInfo", () => {
|
||||
const info = binaryInfo();
|
||||
|
||||
expect(info.version).toBe(getChromiumVersion());
|
||||
expect(info.platform).toMatch(/^(linux|darwin|windows)-(x64|arm64)$/);
|
||||
expect(info.platform).toMatch(/^(linux|darwin)-(x64|arm64)$/);
|
||||
expect(info.binaryPath).toBeTruthy();
|
||||
expect(typeof info.installed).toBe("boolean");
|
||||
expect(info.cacheDir).toContain("cloakbrowser");
|
||||
|
||||
+6
-90
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import {
|
||||
CHROMIUM_VERSION,
|
||||
getChromiumVersion,
|
||||
@@ -8,12 +8,7 @@ import {
|
||||
parseVersion,
|
||||
versionNewer,
|
||||
} from "../src/config.js";
|
||||
import {
|
||||
checkWrapperUpdate,
|
||||
getLatestChromiumVersion,
|
||||
parseChecksums,
|
||||
resetWrapperUpdateChecked,
|
||||
} from "../src/download.js";
|
||||
import { getLatestChromiumVersion, parseChecksums } from "../src/download.js";
|
||||
|
||||
describe("version comparison", () => {
|
||||
it("parseVersion handles 4-part versions", () => {
|
||||
@@ -100,7 +95,7 @@ describe("latest version (platform-aware)", () => {
|
||||
{
|
||||
tag_name: "chromium-v145.0.7718.0",
|
||||
draft: false,
|
||||
assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
||||
assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64"]),
|
||||
},
|
||||
]);
|
||||
expect(await getLatestChromiumVersion()).toBe("145.0.7718.0");
|
||||
@@ -116,7 +111,7 @@ describe("latest version (platform-aware)", () => {
|
||||
{
|
||||
tag_name: "chromium-v142.0.7444.175",
|
||||
draft: false,
|
||||
assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
||||
assets: makeAssets(["linux-x64", "darwin-arm64", "darwin-x64"]),
|
||||
},
|
||||
]);
|
||||
const result = await getLatestChromiumVersion();
|
||||
@@ -133,14 +128,14 @@ describe("latest version (platform-aware)", () => {
|
||||
{
|
||||
tag_name: "chromium-v145.0.7718.0",
|
||||
draft: false,
|
||||
assets: [{ name: "cloakbrowser-freebsd-x64.tar.gz" }],
|
||||
assets: [{ name: "cloakbrowser-windows-x64.tar.gz" }],
|
||||
},
|
||||
]);
|
||||
expect(await getLatestChromiumVersion()).toBeNull();
|
||||
});
|
||||
|
||||
it("skips draft releases", async () => {
|
||||
const all = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"];
|
||||
const all = ["linux-x64", "darwin-arm64", "darwin-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) },
|
||||
@@ -154,85 +149,6 @@ describe("latest version (platform-aware)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("wrapper update check", () => {
|
||||
beforeEach(() => {
|
||||
resetWrapperUpdateChecked();
|
||||
delete process.env.CLOAKBROWSER_AUTO_UPDATE;
|
||||
delete process.env.CLOAKBROWSER_DOWNLOAD_URL;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
delete process.env.CLOAKBROWSER_AUTO_UPDATE;
|
||||
delete process.env.CLOAKBROWSER_DOWNLOAD_URL;
|
||||
});
|
||||
|
||||
it("warns when newer version available", async () => {
|
||||
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ version: "99.0.0" }),
|
||||
} as Response);
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(spy).toHaveBeenCalledOnce();
|
||||
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("Update available"));
|
||||
});
|
||||
|
||||
it("silent when current version", async () => {
|
||||
const { WRAPPER_VERSION } = await import("../src/config.js");
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ version: WRAPPER_VERSION }),
|
||||
} as Response);
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("disabled by CLOAKBROWSER_AUTO_UPDATE=false", async () => {
|
||||
process.env.CLOAKBROWSER_AUTO_UPDATE = "false";
|
||||
const spy = vi.spyOn(globalThis, "fetch");
|
||||
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("disabled by CLOAKBROWSER_DOWNLOAD_URL", async () => {
|
||||
process.env.CLOAKBROWSER_DOWNLOAD_URL = "https://mirror.example.com";
|
||||
const spy = vi.spyOn(globalThis, "fetch");
|
||||
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("silent on network error", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("timeout"));
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs only once per process", async () => {
|
||||
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ version: "0.0.1" }),
|
||||
} as Response);
|
||||
|
||||
await checkWrapperUpdate();
|
||||
await checkWrapperUpdate();
|
||||
|
||||
expect(spy).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseChecksums", () => {
|
||||
// Valid 64-char hex strings for testing
|
||||
const HASH_A = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
|
||||
+3
-10
@@ -17,22 +17,15 @@ keywords = [
|
||||
"browser",
|
||||
"chromium",
|
||||
"playwright",
|
||||
"puppeteer",
|
||||
"scraping",
|
||||
"web-scraping",
|
||||
"anti-detect",
|
||||
"antidetect",
|
||||
"undetected",
|
||||
"bot-detection",
|
||||
"fingerprint",
|
||||
"recaptcha",
|
||||
"cloudflare",
|
||||
"turnstile",
|
||||
"datadome",
|
||||
"captcha",
|
||||
"headless",
|
||||
"bot-detection",
|
||||
"fingerprint",
|
||||
"web-scraping",
|
||||
"automation",
|
||||
"ai-agent",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
|
||||
+5
-67
@@ -19,7 +19,6 @@ from cloakbrowser.config import (
|
||||
get_platform_tag,
|
||||
)
|
||||
from cloakbrowser.download import (
|
||||
_check_wrapper_update,
|
||||
_get_latest_chromium_version,
|
||||
_parse_checksums,
|
||||
_should_check_for_update,
|
||||
@@ -166,7 +165,7 @@ class TestGetLatestVersion:
|
||||
{
|
||||
"tag_name": "chromium-v145.0.7718.0",
|
||||
"draft": False,
|
||||
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
||||
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64"]),
|
||||
},
|
||||
]
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
@@ -187,7 +186,7 @@ class TestGetLatestVersion:
|
||||
{
|
||||
"tag_name": "chromium-v142.0.7444.175",
|
||||
"draft": False,
|
||||
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
||||
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64"]),
|
||||
},
|
||||
]
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
@@ -202,7 +201,7 @@ class TestGetLatestVersion:
|
||||
|
||||
def test_skips_draft_releases(self):
|
||||
mock_response = MagicMock()
|
||||
all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]
|
||||
all_platforms = ["linux-x64", "darwin-arm64", "darwin-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 +214,7 @@ class TestGetLatestVersion:
|
||||
|
||||
def test_skips_non_chromium_tags(self):
|
||||
mock_response = MagicMock()
|
||||
all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]
|
||||
all_platforms = ["linux-x64", "darwin-arm64", "darwin-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 +232,7 @@ class TestGetLatestVersion:
|
||||
{
|
||||
"tag_name": "chromium-v145.0.7718.0",
|
||||
"draft": False,
|
||||
"assets": [{"name": "cloakbrowser-freebsd-x64.tar.gz"}],
|
||||
"assets": [{"name": "cloakbrowser-windows-x64.tar.gz"}],
|
||||
},
|
||||
]
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
@@ -248,67 +247,6 @@ class TestGetLatestVersion:
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestWrapperUpdateCheck:
|
||||
"""Tests for _check_wrapper_update (PyPI version check)."""
|
||||
|
||||
def setup_method(self):
|
||||
import cloakbrowser.download as dl
|
||||
dl._wrapper_update_checked = False
|
||||
|
||||
def test_warns_when_newer_version_available(self, caplog):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {"info": {"version": "99.0.0"}}
|
||||
mock_resp.raise_for_status = MagicMock()
|
||||
|
||||
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp):
|
||||
import logging
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_check_wrapper_update()
|
||||
assert "Update available" in caplog.text
|
||||
assert "99.0.0" in caplog.text
|
||||
|
||||
def test_silent_when_current(self, caplog):
|
||||
import cloakbrowser.download as dl
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {"info": {"version": dl._wrapper_version}}
|
||||
mock_resp.raise_for_status = MagicMock()
|
||||
|
||||
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp):
|
||||
import logging
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_check_wrapper_update()
|
||||
assert "Update available" not in caplog.text
|
||||
|
||||
def test_disabled_by_auto_update_env(self):
|
||||
with patch.dict(os.environ, {"CLOAKBROWSER_AUTO_UPDATE": "false"}):
|
||||
with patch("cloakbrowser.download.httpx.get") as mock_get:
|
||||
_check_wrapper_update()
|
||||
mock_get.assert_not_called()
|
||||
|
||||
def test_disabled_by_custom_download_url(self):
|
||||
with patch.dict(os.environ, {"CLOAKBROWSER_DOWNLOAD_URL": "https://mirror.example.com"}):
|
||||
with patch("cloakbrowser.download.httpx.get") as mock_get:
|
||||
_check_wrapper_update()
|
||||
mock_get.assert_not_called()
|
||||
|
||||
def test_network_error_silent(self, caplog):
|
||||
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
||||
import logging
|
||||
with caplog.at_level(logging.WARNING):
|
||||
_check_wrapper_update()
|
||||
assert "Update available" not in caplog.text
|
||||
|
||||
def test_runs_only_once(self):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {"info": {"version": "0.0.1"}}
|
||||
mock_resp.raise_for_status = MagicMock()
|
||||
|
||||
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp) as mock_get:
|
||||
_check_wrapper_update()
|
||||
_check_wrapper_update()
|
||||
assert mock_get.call_count == 1
|
||||
|
||||
|
||||
class TestParseChecksums:
|
||||
HASH_A = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
HASH_B = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
||||
|
||||
Reference in New Issue
Block a user