mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f46f8e9364 | ||
|
|
592b3d5661 | ||
|
|
a0a8210e35 | ||
|
|
468964ff30 | ||
|
|
c1b93e634b | ||
|
|
5ccb4a32a5 | ||
|
|
49d80d3b57 | ||
|
|
2813b3dc4c | ||
|
|
6550f3ad6c | ||
|
|
132cafe13c | ||
|
|
6c94b9e985 | ||
|
|
fdc1ae0484 | ||
|
|
2ded0c1866 |
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or detection issue
|
||||
labels: bug
|
||||
---
|
||||
|
||||
Description: <!-- What happened? What did you expect? -->
|
||||
|
||||
CloakBrowser version: <!-- pip show cloakbrowser / npm list cloakbrowser -->
|
||||
|
||||
Wrapper: <!-- Python or JavaScript -->
|
||||
|
||||
Environment: <!-- OS, Docker y/n, base image, architecture -->
|
||||
|
||||
Launch options:
|
||||
|
||||
|
||||
Tested with a different IP or proxy? <!-- Yes (same result) / Yes (works with different IP) / No -->
|
||||
|
||||
Works outside Docker / on host machine? <!-- Yes / No / Not using Docker -->
|
||||
|
||||
Steps to reproduce:
|
||||
|
||||
|
||||
Error output / screenshots:
|
||||
|
||||
Dockerfile (if applicable):
|
||||
|
||||
Additional notes:
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Attest build provenance
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
cloakbrowser-*.tar.gz
|
||||
|
||||
@@ -10,8 +10,8 @@ jobs:
|
||||
python:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install dependencies
|
||||
@@ -22,8 +22,8 @@ jobs:
|
||||
javascript:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install and build
|
||||
|
||||
@@ -24,15 +24,15 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Python tests
|
||||
run: |
|
||||
pip install -e ".[dev]" pytest pytest-asyncio
|
||||
pytest tests/ -v -m "not slow"
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 22
|
||||
- name: JavaScript tests
|
||||
@@ -42,8 +42,8 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Check tag matches package versions
|
||||
@@ -62,8 +62,8 @@ jobs:
|
||||
permissions:
|
||||
id-token: write # OIDC trusted publishing — no PYPI_TOKEN needed
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Build
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
pip install build
|
||||
python -m build
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1
|
||||
|
||||
publish-npm:
|
||||
needs: [test, validate-version]
|
||||
@@ -80,8 +80,8 @@ jobs:
|
||||
permissions:
|
||||
id-token: write # OIDC trusted publishing + provenance — no NPM_TOKEN needed
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -102,20 +102,20 @@ jobs:
|
||||
attestations: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- name: Extract version
|
||||
run: |
|
||||
VERSION=$(python -c 'import re; print(re.search(r"__version__\s*=\s*[\"'\'']([^\"'\'']+)", open("cloakbrowser/_version.py").read()).group(1))')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PAT }}
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -125,11 +125,11 @@ jobs:
|
||||
cloakhq/cloakbrowser:latest
|
||||
provenance: true
|
||||
sbom: true
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
- name: Sign image
|
||||
run: cosign sign --yes cloakhq/cloakbrowser@${{ steps.build.outputs.digest }}
|
||||
- name: Attest build provenance
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: index.docker.io/cloakhq/cloakbrowser
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
@@ -6,6 +6,26 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
|
||||
---
|
||||
|
||||
## [0.3.19] — 2026-03-30
|
||||
|
||||
- **[binary]** Upgrade Linux x64 build to 145.0.7632.159.8 — 42 source-level C++ patches (up from 33)
|
||||
- **[binary]** 9 new fingerprint patches covering additional browser APIs and cross-platform consistency
|
||||
- **[binary]** New `--fingerprint-noise` flag — disable noise injection while keeping deterministic fingerprint seed active
|
||||
- **[binary]** Improved fingerprint noise reliability and determinism across all patched APIs
|
||||
- **[binary]** Expanded platform-aware fingerprint spoofing for more realistic cross-platform profiles
|
||||
- **[binary]** Font rendering and detection accuracy improvements for Windows profiles
|
||||
- **[binary]** Removed experimental patches that caused compatibility issues with certain anti-bot systems
|
||||
- **[binary]** Docker/VNC environment compatibility improvements
|
||||
- **[wrapper]** Fix Playwright cleanup — `pw.stop()` now runs even if `browser.close()` raises or is cancelled (fixes #60, thanks [@dgtlmoon](https://github.com/dgtlmoon))
|
||||
- **[meta]** Pin GitHub Actions to commit SHAs, add Dependabot for automated dependency updates
|
||||
|
||||
## [0.3.18] — 2026-03-15
|
||||
|
||||
- **[wrapper]** Fix welcome banner printing to stdout — now writes to stderr so it won't corrupt JSON output in programmatic usage (fixes #59)
|
||||
- **[wrapper]** Fix `cloakserve` Docker WebGL by adding `--ignore-gpu-blocklist` flag
|
||||
- **[docs]** Add Crawlee integration example
|
||||
- **[meta]** Add GitHub issue template for bug reports
|
||||
|
||||
## [0.3.17] — 2026-03-15
|
||||
|
||||
- **[binary]** Windows x64 build upgraded to 145.0.7632.159.7 — 33 source-level C++ patches, matching Linux
|
||||
|
||||
@@ -501,6 +501,8 @@ const browser = await launch({
|
||||
|
||||
Access the original un-patched Playwright page at `page._original` if you need raw speed for a specific call.
|
||||
|
||||
> **Note:** Always use `page.click(selector)`, `page.type(selector, text)`, `page.hover(selector)`, or `page.locator(selector).*` — these go through the full humanize pipeline. Avoid `page.query_selector()` — `ElementHandle` objects bypass all patches, so mouse movement teleports, keyboard events fire without timing, and scroll has no human curve.
|
||||
|
||||
> Contributed by [@evelaa123](https://github.com/evelaa123) — full Playwright API coverage.
|
||||
|
||||
## Configuration
|
||||
@@ -631,6 +633,7 @@ browser = await launch_async(args=["--remote-debugging-port=9242"])
|
||||
|-----------|-------|----------|---------|
|
||||
| [browser-use](https://github.com/browser-use/browser-use) | 70K | Python | [`browser_use_example.py`](examples/integrations/browser_use_example.py) |
|
||||
| [Crawl4AI](https://github.com/unclecode/crawl4ai) | 58K | Python | [`crawl4ai_example.py`](examples/integrations/crawl4ai_example.py) |
|
||||
| [Crawlee](https://github.com/apify/crawlee-python) | 8.6K | Python | [`crawlee_example.py`](examples/integrations/crawlee_example.py) |
|
||||
| [Scrapling](https://github.com/D4Vinci/Scrapling) | 21K | Python | [`scrapling_example.py`](examples/integrations/scrapling_example.py) |
|
||||
| [Stagehand](https://github.com/browserbase/stagehand) | 21K | TypeScript | [`stagehand.ts`](js/examples/stagehand.ts) |
|
||||
| [LangChain](https://github.com/langchain-ai/langchain) | 100K+ | Python | [`langchain_loader.py`](examples/integrations/langchain_loader.py) |
|
||||
|
||||
@@ -33,6 +33,9 @@ chrome_args = [
|
||||
"--disable-popup-blocking",
|
||||
"--disable-background-networking",
|
||||
"--metrics-recording-only",
|
||||
# GPU blocklist bypass: Chromium blocks WebGL on software GPUs in
|
||||
# Docker/Xvfb. Without this, WebGL vendor/renderer spoofing fails. #58
|
||||
"--ignore-gpu-blocklist",
|
||||
] + get_default_stealth_args() + sys.argv[1:]
|
||||
|
||||
chrome = subprocess.Popen(chrome_args)
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.17"
|
||||
__version__ = "0.3.19"
|
||||
|
||||
+20
-10
@@ -120,8 +120,10 @@ def launch(
|
||||
_original_close = browser.close
|
||||
|
||||
def _close_with_cleanup() -> None:
|
||||
_original_close()
|
||||
pw.stop()
|
||||
try:
|
||||
_original_close()
|
||||
finally:
|
||||
pw.stop()
|
||||
|
||||
browser.close = _close_with_cleanup
|
||||
|
||||
@@ -203,8 +205,10 @@ async def launch_async( # noqa: C901
|
||||
_original_close = browser.close
|
||||
|
||||
async def _close_with_cleanup() -> None:
|
||||
await _original_close()
|
||||
await pw.stop()
|
||||
try:
|
||||
await _original_close()
|
||||
finally:
|
||||
await pw.stop()
|
||||
|
||||
browser.close = _close_with_cleanup
|
||||
|
||||
@@ -314,8 +318,10 @@ def launch_persistent_context(
|
||||
_original_close = context.close
|
||||
|
||||
def _close_with_cleanup() -> None:
|
||||
_original_close()
|
||||
pw.stop()
|
||||
try:
|
||||
_original_close()
|
||||
finally:
|
||||
pw.stop()
|
||||
|
||||
context.close = _close_with_cleanup
|
||||
|
||||
@@ -427,8 +433,10 @@ async def launch_persistent_context_async(
|
||||
_original_close = context.close
|
||||
|
||||
async def _close_with_cleanup() -> None:
|
||||
await _original_close()
|
||||
await pw.stop()
|
||||
try:
|
||||
await _original_close()
|
||||
finally:
|
||||
await pw.stop()
|
||||
|
||||
context.close = _close_with_cleanup
|
||||
|
||||
@@ -514,8 +522,10 @@ def launch_context(
|
||||
_original_ctx_close = context.close
|
||||
|
||||
def _close_context_with_cleanup() -> None:
|
||||
_original_ctx_close()
|
||||
browser.close()
|
||||
try:
|
||||
_original_ctx_close()
|
||||
finally:
|
||||
browser.close()
|
||||
|
||||
context.close = _close_context_with_cleanup
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ from ._version import __version__
|
||||
# CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||
# Use get_chromium_version() for the current platform's actual version.
|
||||
# ---------------------------------------------------------------------------
|
||||
CHROMIUM_VERSION = "145.0.7632.159.7"
|
||||
CHROMIUM_VERSION = "145.0.7632.159.8"
|
||||
|
||||
PLATFORM_CHROMIUM_VERSIONS: dict[str, str] = {
|
||||
"linux-x64": "145.0.7632.159.7",
|
||||
"linux-x64": "145.0.7632.159.8",
|
||||
"linux-arm64": "145.0.7632.159.7",
|
||||
"darwin-arm64": "145.0.7632.109.2",
|
||||
"darwin-x64": "145.0.7632.109.2",
|
||||
|
||||
@@ -12,6 +12,7 @@ import os
|
||||
import platform
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import threading
|
||||
@@ -55,14 +56,14 @@ def _show_welcome() -> None:
|
||||
marker = get_cache_dir() / ".welcome_shown"
|
||||
if marker.exists():
|
||||
return
|
||||
print()
|
||||
print(" CloakBrowser — stealth Chromium for automation")
|
||||
print(" https://github.com/CloakHQ/CloakBrowser")
|
||||
print()
|
||||
print(" Issues? https://github.com/CloakHQ/CloakBrowser/issues")
|
||||
print(" Donate? https://ko-fi.com/cloakhq")
|
||||
print(" Star us if CloakBrowser helps your project!")
|
||||
print()
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write(" CloakBrowser — stealth Chromium for automation\n")
|
||||
sys.stderr.write(" https://github.com/CloakHQ/CloakBrowser\n")
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write(" Issues? https://github.com/CloakHQ/CloakBrowser/issues\n")
|
||||
sys.stderr.write(" Donate? https://ko-fi.com/cloakhq\n")
|
||||
sys.stderr.write(" Star us if CloakBrowser helps your project!\n")
|
||||
sys.stderr.write("\n")
|
||||
try:
|
||||
marker.parent.mkdir(parents=True, exist_ok=True)
|
||||
marker.write_text("")
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Crawlee + CloakBrowser: stealth web crawling with PlaywrightCrawler.
|
||||
|
||||
Uses a custom BrowserPlugin to swap Crawlee's default Chromium
|
||||
for CloakBrowser's patched binary with source-level fingerprint patches.
|
||||
|
||||
Requires: pip install cloakbrowser "crawlee[playwright]"
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from cloakbrowser.config import IGNORE_DEFAULT_ARGS, get_default_stealth_args
|
||||
from cloakbrowser.download import ensure_binary
|
||||
from typing_extensions import override
|
||||
|
||||
from crawlee.browsers import (
|
||||
BrowserPool,
|
||||
PlaywrightBrowserController,
|
||||
PlaywrightBrowserPlugin,
|
||||
)
|
||||
from crawlee.crawlers import PlaywrightCrawler, PlaywrightCrawlingContext
|
||||
|
||||
|
||||
class CloakBrowserPlugin(PlaywrightBrowserPlugin):
|
||||
"""Browser plugin that uses CloakBrowser's patched Chromium,
|
||||
but otherwise keeps the functionality of PlaywrightBrowserPlugin.
|
||||
"""
|
||||
|
||||
@override
|
||||
async def new_browser(self) -> PlaywrightBrowserController:
|
||||
if not self._playwright:
|
||||
raise RuntimeError('Playwright browser plugin is not initialized.')
|
||||
|
||||
binary_path = ensure_binary()
|
||||
stealth_args = get_default_stealth_args()
|
||||
|
||||
# Merge CloakBrowser stealth args with any user-provided launch options.
|
||||
launch_options = dict(self._browser_launch_options)
|
||||
launch_options.pop('executable_path', None)
|
||||
launch_options.pop('chromium_sandbox', None)
|
||||
existing_args = list(launch_options.pop('args', []))
|
||||
launch_options['args'] = [*existing_args, *stealth_args]
|
||||
|
||||
return PlaywrightBrowserController(
|
||||
browser=await self._playwright.chromium.launch(
|
||||
executable_path=binary_path,
|
||||
ignore_default_args=IGNORE_DEFAULT_ARGS,
|
||||
**launch_options,
|
||||
),
|
||||
max_open_pages_per_browser=1,
|
||||
# CloakBrowser handles fingerprints at the binary level.
|
||||
header_generator=None,
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
crawler = PlaywrightCrawler(
|
||||
max_requests_per_crawl=10,
|
||||
browser_pool=BrowserPool(plugins=[CloakBrowserPlugin()]),
|
||||
)
|
||||
|
||||
@crawler.router.default_handler
|
||||
async def request_handler(context: PlaywrightCrawlingContext) -> None:
|
||||
context.log.info(f'Processing {context.request.url} ...')
|
||||
title = await context.page.title()
|
||||
await context.push_data({'url': context.request.url, 'title': title})
|
||||
await context.enqueue_links()
|
||||
|
||||
await crawler.run(['https://example.com'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.3.17",
|
||||
"version": "0.3.19",
|
||||
"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",
|
||||
|
||||
+2
-2
@@ -27,10 +27,10 @@ export { WRAPPER_VERSION };
|
||||
// CHROMIUM_VERSION is the latest across all platforms (for display/reference).
|
||||
// Use getChromiumVersion() for the current platform's actual version.
|
||||
// ---------------------------------------------------------------------------
|
||||
export const CHROMIUM_VERSION = "145.0.7632.159.7";
|
||||
export const CHROMIUM_VERSION = "145.0.7632.159.8";
|
||||
|
||||
export const PLATFORM_CHROMIUM_VERSIONS: Record<string, string> = {
|
||||
"linux-x64": "145.0.7632.159.7",
|
||||
"linux-x64": "145.0.7632.159.8",
|
||||
"linux-arm64": "145.0.7632.159.7",
|
||||
"darwin-arm64": "145.0.7632.109.2",
|
||||
"darwin-x64": "145.0.7632.109.2",
|
||||
|
||||
+8
-8
@@ -146,14 +146,14 @@ export async function checkForUpdate(): Promise<string | null> {
|
||||
function showWelcome(): void {
|
||||
const marker = path.join(getCacheDir(), ".welcome_shown");
|
||||
if (fs.existsSync(marker)) return;
|
||||
console.log();
|
||||
console.log(" CloakBrowser — stealth Chromium for automation");
|
||||
console.log(" https://github.com/CloakHQ/CloakBrowser");
|
||||
console.log();
|
||||
console.log(" Issues? https://github.com/CloakHQ/CloakBrowser/issues");
|
||||
console.log(" Donate? https://ko-fi.com/cloakhq");
|
||||
console.log(" Star us if CloakBrowser helps your project!");
|
||||
console.log();
|
||||
console.error();
|
||||
console.error(" CloakBrowser — stealth Chromium for automation");
|
||||
console.error(" https://github.com/CloakHQ/CloakBrowser");
|
||||
console.error();
|
||||
console.error(" Issues? https://github.com/CloakHQ/CloakBrowser/issues");
|
||||
console.error(" Donate? https://ko-fi.com/cloakhq");
|
||||
console.error(" Star us if CloakBrowser helps your project!");
|
||||
console.error();
|
||||
try {
|
||||
fs.mkdirSync(getCacheDir(), { recursive: true });
|
||||
fs.writeFileSync(marker, "");
|
||||
|
||||
Reference in New Issue
Block a user