mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2eb0ef21a | ||
|
|
2d7894ad68 | ||
|
|
efab732142 | ||
|
|
924f401265 | ||
|
|
6ea8391fac |
@@ -45,3 +45,7 @@ AGENTS.md
|
||||
|
||||
# Private docs (launch posts, strategy)
|
||||
docs/
|
||||
|
||||
# Release scripts
|
||||
publish.sh
|
||||
.env
|
||||
|
||||
+5
-2
@@ -1,11 +1,14 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Playwright system deps
|
||||
# Chromium system deps (matches fingerprint-chromium 142+ requirements)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
||||
libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 \
|
||||
libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \
|
||||
libcairo2 libasound2 libx11-xcb1 \
|
||||
libcairo2 libasound2 libx11-xcb1 libfontconfig1 libx11-6 \
|
||||
libxcb1 libxext6 libxshmfence1 \
|
||||
libglib2.0-0 libgtk-3-0 libpangocairo-1.0-0 libcairo-gobject2 \
|
||||
libgdk-pixbuf-2.0-0 libxss1 libxtst6 fonts-liberation \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/CloakHQ/CloakBrowser/main/images/logo.png" width="500" alt="CloakBrowser">
|
||||
</p>
|
||||
|
||||
# CloakBrowser
|
||||
|
||||
[](https://pypi.org/project/cloakbrowser/)
|
||||
[](https://pypi.org/project/cloakbrowser/)
|
||||
[](LICENSE)
|
||||
[](https://github.com/CloakHQ/CloakBrowser)
|
||||
[](https://github.com/CloakHQ/CloakBrowser)
|
||||
|
||||
**Stealth Chromium that passes every bot detection test.**
|
||||
|
||||
Drop-in Playwright replacement. Same API, same code — just swap the import. Your browser now scores **0.9 on reCAPTCHA v3**, passes **Cloudflare Turnstile**, and clears **14 out of 14** stealth detection tests.
|
||||
|
||||
> CloakBrowser is an open-source Python library that gives you an undetectable browser for web automation and data collection. It wraps a custom-built Chromium binary with 16 source-level fingerprint patches — modifications compiled directly into the browser's C++ code, not bolted on via JavaScript injection or config flags.
|
||||
>
|
||||
> Most stealth tools (playwright-stealth, undetected-chromedriver, puppeteer-extra) work at the config level: they override JavaScript properties, set browser flags, or inject scripts to mask automation signals. These approaches break every time Chrome updates its detection surface — and Cloudflare, reCAPTCHA, and other bot detection systems update constantly.
|
||||
>
|
||||
> CloakBrowser takes a fundamentally different approach. The fingerprint patches modify Chromium's rendering pipeline at the source level: canvas output, WebGL renderer strings, audio processing fingerprints, font enumeration, hardware concurrency, client rectangles, GPU info, timezone handling, and the WebDriver flag are all patched in C++ before compilation. The resulting binary produces fingerprints indistinguishable from a real user's Chrome installation. TLS fingerprints (ja3n, ja4, akamai hash) match genuine Chrome exactly because the browser IS Chrome — just with different fingerprint values compiled in.
|
||||
>
|
||||
> The result: a 0.9 score on Google's reCAPTCHA v3 (human-level, versus 0.1 for stock Playwright), clean passes on Cloudflare Turnstile managed challenges, zero bot flags on FingerprintJS, BrowserScan, and deviceandbrowserinfo.com, and undetected CDP (Chrome DevTools Protocol) usage. 14 out of 14 major detection tests passed.
|
||||
>
|
||||
> CloakBrowser fills the vacuum left by Camoufox (the popular Firefox-based stealth browser whose maintainer stepped down in 2025). Unlike Camoufox, CloakBrowser is Chromium-based — meaning native Playwright API support, a massive ecosystem of existing tools and scripts, and TLS fingerprints that match the browser 65%+ of the internet actually uses.
|
||||
>
|
||||
> Install with pip, write standard Playwright code, and let the binary handle the stealth. No new API to learn. No configuration to tune. No CAPTCHA-solving services to pay for. Just a browser that works on sites where others get blocked.
|
||||
- 🔒 **16 source-level C++ patches** — not JS injection, not config flags
|
||||
- 🎯 **0.9 reCAPTCHA v3 score** — human-level, server-verified
|
||||
- ☁️ **Passes Cloudflare Turnstile**, FingerprintJS, BrowserScan — 14/14 tests
|
||||
- 🔄 **Drop-in Playwright replacement** — same API, swap one import
|
||||
- 📦 **`pip install cloakbrowser`** — binary auto-downloads, zero config
|
||||
- 🦊 **Fills the Camoufox vacuum** — Chromium-based, actively maintained
|
||||
|
||||
```python
|
||||
from cloakbrowser import launch
|
||||
@@ -39,15 +40,13 @@ On first run, the stealth Chromium binary is automatically downloaded (~200MB, c
|
||||
|
||||
## Why CloakBrowser?
|
||||
|
||||
Every bot detection system — reCAPTCHA, Cloudflare Turnstile, ShieldSquare, FingerprintJS — identifies automation browsers through **browser fingerprinting**: canvas rendering, WebGL output, audio processing, font enumeration, and dozens of other signals.
|
||||
|
||||
Tools like `playwright-stealth` or `undetected-chromedriver` try to fix this with **config-level patches** — JavaScript overrides, flag tweaks, UA spoofing. These work until the next Chrome update breaks them.
|
||||
|
||||
CloakBrowser patches **Chromium source code** — the fingerprint signals are modified at the C++ level, compiled into the binary. Detection sites see a real browser because, at the binary level, it *is* a real browser with different fingerprint values.
|
||||
- **Config-level patches break** — `playwright-stealth`, `undetected-chromedriver`, and `puppeteer-extra` inject JavaScript or tweak flags. Every Chrome update breaks them. Antibot systems detect the patches themselves.
|
||||
- **CloakBrowser patches Chromium source code** — fingerprints are modified at the C++ level, compiled into the binary. Detection sites see a real browser because it *is* a real browser.
|
||||
- **One line to switch** — same Playwright API, no new abstractions, no CAPTCHA-solving services.
|
||||
|
||||
## Test Results
|
||||
|
||||
All tests verified against live detection services. Last tested: Feb 2026 (Chromium 145).
|
||||
All tests verified against live detection services. Last tested: Feb 2026 (Chromium 142).
|
||||
|
||||
| Detection Service | Stock Playwright | CloakBrowser | Notes |
|
||||
|---|---|---|---|
|
||||
@@ -62,7 +61,7 @@ All tests verified against live detection services. Last tested: Feb 2026 (Chrom
|
||||
| `navigator.webdriver` | `true` | **`false`** | Source-level patch |
|
||||
| `navigator.plugins.length` | 0 | **5** | Real plugin list |
|
||||
| `window.chrome` | `undefined` | **`object`** | Present like real Chrome |
|
||||
| UA string | `HeadlessChrome` | **`Chrome/145.0.0.0`** | No headless leak |
|
||||
| UA string | `HeadlessChrome` | **`Chrome/142.0.0.0`** | No headless leak |
|
||||
| CDP detection | Detected | **Not detected** | `isAutomatedWithCDP: false` |
|
||||
| TLS fingerprint | Mismatch | **Identical to Chrome** | ja3n/ja4/akamai match |
|
||||
|
||||
@@ -71,17 +70,22 @@ All tests verified against live detection services. Last tested: Feb 2026 (Chrom
|
||||
### Proof
|
||||
|
||||
<p align="center">
|
||||
<img src="images/turnstile_non_interactive.png" width="600" alt="Cloudflare Turnstile — Success">
|
||||
<img src="https://raw.githubusercontent.com/CloakHQ/CloakBrowser/main/images/recaptcha_v3_score_09.png" width="600" alt="reCAPTCHA v3 — Score 0.9">
|
||||
<br><em>reCAPTCHA v3 score 0.9 — server-side verified (human-level)</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/CloakHQ/CloakBrowser/main/images/turnstile_non_interactive.png" width="600" alt="Cloudflare Turnstile — Success">
|
||||
<br><em>Cloudflare Turnstile non-interactive challenge — auto-resolved</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="images/browserscan_normal.png" width="600" alt="BrowserScan — Normal">
|
||||
<img src="https://raw.githubusercontent.com/CloakHQ/CloakBrowser/main/images/browserscan_normal.png" width="600" alt="BrowserScan — Normal">
|
||||
<br><em>BrowserScan bot detection — NORMAL (4/4 checks passed)</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="images/fingerprintjs_pass.png" width="600" alt="FingerprintJS — Passed">
|
||||
<img src="https://raw.githubusercontent.com/CloakHQ/CloakBrowser/main/images/fingerprintjs_pass.png" width="600" alt="FingerprintJS — Passed">
|
||||
<br><em>FingerprintJS web-scraping demo — data served, not blocked</em>
|
||||
</p>
|
||||
|
||||
@@ -172,7 +176,7 @@ from cloakbrowser import binary_info, clear_cache, ensure_binary
|
||||
|
||||
# Check binary installation status
|
||||
print(binary_info())
|
||||
# {'version': '145.0.7723.116', 'platform': 'darwin-arm64', 'installed': True, ...}
|
||||
# {'version': '142.0.7444.175', 'platform': 'linux-x64', 'installed': True, ...}
|
||||
|
||||
# Force re-download
|
||||
clear_cache()
|
||||
@@ -233,6 +237,57 @@ See the [`examples/`](examples/) directory:
|
||||
- [`recaptcha_score.py`](examples/recaptcha_score.py) — Check your reCAPTCHA v3 score
|
||||
- [`stealth_test.py`](examples/stealth_test.py) — Run against all detection services
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Linux x64 binary | ✅ Released |
|
||||
| macOS arm64 (Apple Silicon) | 🔜 In progress |
|
||||
| Chromium 145 build | 🔜 In progress |
|
||||
| Fingerprint rotation per session | 📋 Planned |
|
||||
| Built-in proxy rotation | 📋 Planned |
|
||||
| Windows support | 📋 Planned |
|
||||
|
||||
> ⭐ **Star this repo** to get notified when Chromium 145 and macOS builds drop.
|
||||
|
||||
## Docker
|
||||
|
||||
A ready-to-use [`Dockerfile`](Dockerfile) is included. It installs system deps, the package, and pre-downloads the stealth binary during build:
|
||||
|
||||
```bash
|
||||
docker build -t cloakbrowser .
|
||||
docker run --rm cloakbrowser python examples/basic.py
|
||||
```
|
||||
|
||||
The key steps in the Dockerfile:
|
||||
1. **System deps** — Chromium requires ~15 shared libraries (`libnss3`, `libgbm1`, etc.)
|
||||
2. **`pip install .`** — installs CloakBrowser + Playwright
|
||||
3. **`ensure_binary()`** — downloads the stealth Chromium binary at build time (~200MB), so containers start instantly
|
||||
|
||||
To extend with your own script, just add a `COPY` + `CMD`:
|
||||
|
||||
```dockerfile
|
||||
FROM cloakbrowser
|
||||
COPY your_script.py /app/
|
||||
CMD ["python", "your_script.py"]
|
||||
```
|
||||
|
||||
**Note:** If you run CloakBrowser inside a web server with uvloop (e.g., `uvicorn[standard]`), use `--loop asyncio` to avoid subprocess pipe hangs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Binary download fails / timeout**
|
||||
Set a custom download URL or use a local binary:
|
||||
```bash
|
||||
export CLOAKBROWSER_BINARY_PATH=/path/to/your/chrome
|
||||
```
|
||||
|
||||
**"playwright install" vs CloakBrowser binary**
|
||||
You do NOT need `playwright install chromium`. CloakBrowser downloads its own binary. You only need Playwright's system deps:
|
||||
```bash
|
||||
playwright install-deps chromium
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Is this legal?**
|
||||
@@ -248,7 +303,7 @@ A: Possibly. Bot detection is an arms race. Source-level patches are harder to d
|
||||
A: Yes. Pass `proxy="http://user:pass@host:port"` to `launch()`.
|
||||
|
||||
**Q: Can I use this with Docker?**
|
||||
A: Yes. Use `ensure_binary()` in your Dockerfile to pre-download the binary during image build.
|
||||
A: Yes. A ready-to-use Dockerfile is included — see the [Docker](#docker) section above.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Usage:
|
||||
"""
|
||||
|
||||
from .browser import launch, launch_async, launch_context
|
||||
from .config import CHROMIUM_VERSION, DEFAULT_STEALTH_ARGS
|
||||
from .config import CHROMIUM_VERSION, get_default_stealth_args
|
||||
from .download import binary_info, clear_cache, ensure_binary
|
||||
from ._version import __version__
|
||||
|
||||
@@ -24,6 +24,6 @@ __all__ = [
|
||||
"clear_cache",
|
||||
"binary_info",
|
||||
"CHROMIUM_VERSION",
|
||||
"DEFAULT_STEALTH_ARGS",
|
||||
"get_default_stealth_args",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.0"
|
||||
__version__ = "0.1.4"
|
||||
|
||||
@@ -17,7 +17,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from .config import DEFAULT_STEALTH_ARGS
|
||||
from .config import get_default_stealth_args
|
||||
from .download import ensure_binary
|
||||
|
||||
logger = logging.getLogger("cloakbrowser")
|
||||
@@ -63,6 +63,7 @@ def launch(
|
||||
executable_path=binary_path,
|
||||
headless=headless,
|
||||
args=chrome_args,
|
||||
ignore_default_args=["--enable-automation"],
|
||||
**_build_proxy_kwargs(proxy),
|
||||
**kwargs,
|
||||
)
|
||||
@@ -123,6 +124,7 @@ async def launch_async(
|
||||
executable_path=binary_path,
|
||||
headless=headless,
|
||||
args=chrome_args,
|
||||
ignore_default_args=["--enable-automation"],
|
||||
**_build_proxy_kwargs(proxy),
|
||||
**kwargs,
|
||||
)
|
||||
@@ -209,7 +211,7 @@ def _build_args(stealth_args: bool, extra_args: list[str] | None) -> list[str]:
|
||||
"""Combine stealth args with user-provided args."""
|
||||
result = []
|
||||
if stealth_args:
|
||||
result.extend(DEFAULT_STEALTH_ARGS)
|
||||
result.extend(get_default_stealth_args())
|
||||
if extra_args:
|
||||
result.extend(extra_args)
|
||||
return result
|
||||
|
||||
+13
-10
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
from ._version import __version__
|
||||
@@ -17,16 +18,18 @@ CHROMIUM_VERSION = "142.0.7444.175"
|
||||
# Default stealth arguments passed to the patched Chromium binary.
|
||||
# These activate source-level fingerprint patches compiled into the binary.
|
||||
# ---------------------------------------------------------------------------
|
||||
DEFAULT_STEALTH_ARGS: list[str] = [
|
||||
"--no-sandbox",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
# Fingerprint overrides (activate compiled C++ patches)
|
||||
"--fingerprint=98765",
|
||||
"--fingerprint-platform=windows",
|
||||
"--fingerprint-hardware-concurrency=8",
|
||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 4070",
|
||||
]
|
||||
def get_default_stealth_args() -> list[str]:
|
||||
"""Build stealth args with a random fingerprint seed per launch."""
|
||||
seed = random.randint(10000, 99999)
|
||||
return [
|
||||
"--no-sandbox",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
f"--fingerprint={seed}",
|
||||
"--fingerprint-platform=windows",
|
||||
"--fingerprint-hardware-concurrency=8",
|
||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 3070",
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform detection
|
||||
|
||||
+241
-27
@@ -1,57 +1,271 @@
|
||||
"""Run stealth tests against major bot detection services.
|
||||
|
||||
Tests cloakbrowser against multiple detection sites and reports results.
|
||||
Tests cloakbrowser against multiple detection sites, extracts pass/fail
|
||||
verdicts via JS evaluation, and reports results with screenshots.
|
||||
|
||||
Usage:
|
||||
python examples/stealth_test.py
|
||||
python examples/stealth_test.py --headed # watch in real-time
|
||||
python examples/stealth_test.py --no-screenshots
|
||||
python examples/stealth_test.py --proxy http://10.50.96.5:8888
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
from cloakbrowser import launch
|
||||
|
||||
HEADED = "--headed" in sys.argv
|
||||
SCREENSHOTS = "--no-screenshots" not in sys.argv
|
||||
PROXY = None
|
||||
for i, arg in enumerate(sys.argv):
|
||||
if arg == "--proxy" and i + 1 < len(sys.argv):
|
||||
PROXY = sys.argv[i + 1]
|
||||
|
||||
|
||||
def test_bot_sannysoft(page):
|
||||
"""bot.sannysoft.com — classic bot detection checks."""
|
||||
page.goto("https://bot.sannysoft.com", wait_until="networkidle", timeout=30000)
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const rows = document.querySelectorAll('table tr');
|
||||
const data = {};
|
||||
rows.forEach(r => {
|
||||
const cells = r.querySelectorAll('td');
|
||||
if (cells.length >= 2) {
|
||||
const key = cells[0].innerText.trim();
|
||||
const val = cells[1].innerText.trim();
|
||||
const cls = cells[1].className || '';
|
||||
data[key] = {value: val, passed: !cls.includes('failed')};
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}""")
|
||||
|
||||
failed = [k for k, v in results.items() if not v["passed"]]
|
||||
total = len(results)
|
||||
passed = total - len(failed)
|
||||
return {"passed": passed, "total": total, "failed": failed}
|
||||
|
||||
|
||||
def test_bot_incolumitas(page):
|
||||
"""bot.incolumitas.com — comprehensive 30+ check bot detection."""
|
||||
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
|
||||
page.wait_for_timeout(12000) # needs time to run all detection tests
|
||||
|
||||
# Site outputs JSON blocks in page text, not HTML tables
|
||||
results = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
const okMatches = text.match(/"\\w+":\\s*"OK"/g) || [];
|
||||
const failMatches = text.match(/"\\w+":\\s*"FAIL"/g) || [];
|
||||
const failedTests = failMatches.map(m => m.match(/"(\\w+)"/)[1]);
|
||||
return {
|
||||
passed: okMatches.length,
|
||||
failed: failMatches.length,
|
||||
failedTests,
|
||||
total: okMatches.length + failMatches.length
|
||||
};
|
||||
}""")
|
||||
return results
|
||||
|
||||
|
||||
def test_browserscan(page):
|
||||
"""browserscan.net/bot-detection — WebDriver, UA, CDP, Navigator checks."""
|
||||
page.goto("https://www.browserscan.net/bot-detection", wait_until="networkidle", timeout=30000)
|
||||
page.wait_for_timeout(5000)
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const items = document.querySelectorAll('[class*="result"], [class*="item"], [class*="check"]');
|
||||
let normal = 0, abnormal = 0;
|
||||
const text = document.body.innerText;
|
||||
// Count "Normal" vs "Abnormal" verdicts
|
||||
const normalMatches = text.match(/Normal/g);
|
||||
const abnormalMatches = text.match(/Abnormal/g);
|
||||
return {
|
||||
normal: normalMatches ? normalMatches.length : 0,
|
||||
abnormal: abnormalMatches ? abnormalMatches.length : 0,
|
||||
pageText: text.substring(0, 500)
|
||||
};
|
||||
}""")
|
||||
return results
|
||||
|
||||
|
||||
def test_deviceandbrowserinfo(page):
|
||||
"""deviceandbrowserinfo.com/are_you_a_bot — fingerprint + behavioral detection."""
|
||||
page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", wait_until="domcontentloaded", timeout=30000)
|
||||
page.wait_for_timeout(8000)
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
// Site outputs JSON with "isBot": false and detail checks
|
||||
const botMatch = text.match(/"isBot":\\s*(true|false)/);
|
||||
const isBot = botMatch ? botMatch[1] === 'true' : null;
|
||||
const checks = {};
|
||||
const patterns = [
|
||||
'isBot', 'hasBotUserAgent', 'hasWebdriverTrue',
|
||||
'isHeadlessChrome', 'isAutomatedWithCDP', 'hasSuspiciousWeakSignals',
|
||||
'isPlaywright', 'hasInconsistentChromeObject'
|
||||
];
|
||||
patterns.forEach(p => {
|
||||
const match = text.match(new RegExp('"' + p + '":\\s*(true|false)'));
|
||||
if (match) checks[p] = match[1] === 'true';
|
||||
});
|
||||
return {isBot, checks};
|
||||
}""")
|
||||
return results
|
||||
|
||||
|
||||
def test_fingerprintjs(page):
|
||||
"""demo.fingerprint.com/web-scraping — industry-standard bot detection."""
|
||||
page.goto("https://demo.fingerprint.com/web-scraping", wait_until="networkidle", timeout=30000)
|
||||
page.wait_for_timeout(5000)
|
||||
|
||||
# Click search to trigger bot detection — bots get blocked, humans see flights
|
||||
try:
|
||||
page.click("button:has-text('Search')", timeout=5000)
|
||||
page.wait_for_timeout(5000)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
// Bots see error messages; humans see flight prices
|
||||
const hasFlights = text.includes('Price per adult') || text.includes('$');
|
||||
const isBlocked = text.includes('request was blocked') || text.includes('bot visit detected');
|
||||
return {passed: hasFlights && !isBlocked, isBlocked, hasFlights};
|
||||
}""")
|
||||
return results
|
||||
|
||||
|
||||
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="networkidle",
|
||||
timeout=30000,
|
||||
)
|
||||
# Page auto-submits via grecaptcha.execute() — wait for backend response
|
||||
page.wait_for_timeout(8000)
|
||||
|
||||
results = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
// Score appears in JSON response block: "score": 0.9
|
||||
const scoreMatch = text.match(/"score":\\s*(\\d+\\.\\d+)/);
|
||||
return {
|
||||
score: scoreMatch ? parseFloat(scoreMatch[1]) : null,
|
||||
pageText: text.substring(0, 500)
|
||||
};
|
||||
}""")
|
||||
return results
|
||||
|
||||
|
||||
TESTS = [
|
||||
{
|
||||
"name": "bot.sannysoft.com",
|
||||
"url": "https://bot.sannysoft.com",
|
||||
"runner": test_bot_sannysoft,
|
||||
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||
+ (f" (FAILED: {', '.join(r['failed'])})" if r["failed"] else " — ALL GREEN"),
|
||||
"pass": lambda r: len(r["failed"]) == 0,
|
||||
},
|
||||
{
|
||||
"name": "bot.incolumitas.com",
|
||||
"url": "https://bot.incolumitas.com",
|
||||
"check": "Bot detection analysis",
|
||||
"runner": test_bot_incolumitas,
|
||||
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||
+ (f" (FAILED: {', '.join(r.get('failedTests', []))})" if r.get("failed", 0) > 0 else " — ALL GREEN"),
|
||||
"pass": lambda r: r.get("failed", 0) <= 1, # fpscanner.WEBDRIVER false positive expected (all builds)
|
||||
},
|
||||
{
|
||||
"name": "BrowserScan",
|
||||
"url": "https://www.browserscan.net/bot-detection",
|
||||
"check": "Bot detection status",
|
||||
"runner": test_browserscan,
|
||||
"verdict": lambda r: f"Normal: {r['normal']}, Abnormal: {r['abnormal']}",
|
||||
"pass": lambda r: r.get("abnormal", 1) == 0,
|
||||
},
|
||||
{
|
||||
"name": "deviceandbrowserinfo.com",
|
||||
"url": "https://deviceandbrowserinfo.com/are_you_a_bot",
|
||||
"check": "isBot flag",
|
||||
"runner": test_deviceandbrowserinfo,
|
||||
"verdict": lambda r: f"isBot: {r.get('isBot', 'unknown')}"
|
||||
+ (f" checks: {json.dumps(r.get('checks', {}))}" if r.get("checks") else ""),
|
||||
"pass": lambda r: not r.get("isBot", True),
|
||||
},
|
||||
{
|
||||
"name": "FingerprintJS",
|
||||
"url": "https://demo.fingerprint.com/web-scraping",
|
||||
"check": "Bot detection result",
|
||||
"runner": test_fingerprintjs,
|
||||
"verdict": lambda r: "PASSED (flights shown)" if r.get("passed") else "BLOCKED" if r.get("isBlocked") else "NO FLIGHTS",
|
||||
"pass": lambda r: r.get("passed", False),
|
||||
},
|
||||
{
|
||||
"name": "reCAPTCHA v3 (Google)",
|
||||
"url": "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
|
||||
"runner": test_recaptcha,
|
||||
"verdict": lambda r: f"Score: {r.get('score', 'N/A')}",
|
||||
"pass": lambda r: (r.get("score") or 0) >= 0.7,
|
||||
},
|
||||
]
|
||||
|
||||
browser = launch(headless=True)
|
||||
page = browser.new_page()
|
||||
|
||||
print("=" * 60)
|
||||
print("CloakBrowser Stealth Test Suite")
|
||||
print("=" * 60)
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("CloakBrowser Stealth Test Suite")
|
||||
print("=" * 60)
|
||||
print(f"Mode: {'headed' if HEADED else 'headless'}")
|
||||
print(f"Screenshots: {'on' if SCREENSHOTS else 'off'}")
|
||||
print(f"Proxy: {PROXY or 'none'}")
|
||||
print()
|
||||
|
||||
for test in TESTS:
|
||||
print(f"\n--- {test['name']} ---")
|
||||
print(f"URL: {test['url']}")
|
||||
try:
|
||||
page.goto(test["url"], wait_until="networkidle", timeout=30000)
|
||||
page.wait_for_timeout(3000)
|
||||
browser = launch(headless=not HEADED, proxy=PROXY)
|
||||
page = browser.new_page()
|
||||
|
||||
# Screenshot each test
|
||||
filename = f"stealth_test_{test['name'].replace('.', '_').replace(' ', '_')}.png"
|
||||
page.screenshot(path=filename)
|
||||
print(f"Screenshot: {filename}")
|
||||
print(f"Title: {page.title()}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
results_summary = []
|
||||
|
||||
browser.close()
|
||||
for test in TESTS:
|
||||
name = test["name"]
|
||||
print(f"--- {name} ---")
|
||||
print(f"URL: {test['url']}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Tests complete. Check screenshots for results.")
|
||||
print("=" * 60)
|
||||
try:
|
||||
result = test["runner"](page)
|
||||
passed = test["pass"](result)
|
||||
verdict = test["verdict"](result)
|
||||
status = "PASS" if passed else "FAIL"
|
||||
results_summary.append((name, status, verdict))
|
||||
|
||||
print(f"Result: [{status}] {verdict}")
|
||||
|
||||
if SCREENSHOTS:
|
||||
filename = f"stealth_test_{name.replace('.', '_').replace(' ', '_').replace('/', '_')}.png"
|
||||
page.screenshot(path=filename)
|
||||
print(f"Screenshot: {filename}")
|
||||
|
||||
except Exception as e:
|
||||
results_summary.append((name, "ERROR", str(e)))
|
||||
print(f"Error: {e}")
|
||||
|
||||
print()
|
||||
|
||||
browser.close()
|
||||
|
||||
# Summary table
|
||||
print("=" * 60)
|
||||
print("RESULTS SUMMARY")
|
||||
print("=" * 60)
|
||||
for name, status, verdict in results_summary:
|
||||
icon = {"PASS": "+", "FAIL": "!", "ERROR": "x"}[status]
|
||||
print(f" [{icon}] {name}: {verdict}")
|
||||
|
||||
passed_count = sum(1 for _, s, _ in results_summary if s == "PASS")
|
||||
total = len(results_summary)
|
||||
print(f"\n {passed_count}/{total} tests passed")
|
||||
print("=" * 60)
|
||||
|
||||
return 0 if passed_count == total else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
+5
-5
@@ -10,7 +10,7 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
{ name = "cloakbrowser" },
|
||||
{ name = "CloakHQ", email = "cloakhq@pm.me" },
|
||||
]
|
||||
keywords = [
|
||||
"stealth",
|
||||
@@ -47,10 +47,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/CloakHQ/cloakbrowser"
|
||||
Documentation = "https://github.com/CloakHQ/cloakbrowser#readme"
|
||||
Repository = "https://github.com/CloakHQ/cloakbrowser"
|
||||
Issues = "https://github.com/CloakHQ/cloakbrowser/issues"
|
||||
Homepage = "https://github.com/CloakHQ/CloakBrowser"
|
||||
Documentation = "https://github.com/CloakHQ/CloakBrowser#readme"
|
||||
Repository = "https://github.com/CloakHQ/CloakBrowser"
|
||||
Issues = "https://github.com/CloakHQ/CloakBrowser/issues"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "cloakbrowser/_version.py"
|
||||
|
||||
Reference in New Issue
Block a user