release: v0.4.7 — update cloaktest
Publish / test (push) Has been cancelled
Publish / validate-version (push) Has been cancelled
Publish / publish-pypi (push) Has been cancelled
Publish / publish-npm (push) Has been cancelled
Publish / publish-nuget (push) Has been cancelled
Publish / publish-docker (push) Has been cancelled
Publish / test (push) Has been cancelled
Publish / validate-version (push) Has been cancelled
Publish / publish-pypi (push) Has been cancelled
Publish / publish-npm (push) Has been cancelled
Publish / publish-nuget (push) Has been cancelled
Publish / publish-docker (push) Has been cancelled
This commit is contained in:
@@ -8,6 +8,10 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.4.7] — 2026-07-02
|
||||
|
||||
- **[docker]** Update the public `cloaktest` suite to use free-tier-stable bot-detection checks while Pro continues to verify reCAPTCHA v3 at 0.9. The Docker smoke test now runs Sannysoft, Incolumitas, Rebrowser, deviceandbrowserinfo, BrowserScan, and CreepJS lies/noise=false; FingerprintJS and reCAPTCHA v3 are no longer hard-pass checks for the free v146 image.
|
||||
|
||||
## [0.4.6] — 2026-07-02
|
||||
|
||||
- **[wrapper]** The resolved Pro license key is now passed to the browser process at launch (via `CLOAKBROWSER_LICENSE_KEY`) so the Pro binary can authenticate itself, including when you supply a custom `env`. Fixes launch failures on the newest Pro binaries. Python, JS, and .NET.
|
||||
|
||||
@@ -38,11 +38,11 @@ Same API, same code — just swap the import. <strong>3 lines of code, 30 second
|
||||
|
||||
- **58 source-level C++ patches** — canvas, WebGL, audio, fonts, GPU, screen, WebRTC, network timing, automation signals, CDP input behavior
|
||||
- **`humanize=True`** — human-like mouse curves, keyboard timing, and scroll patterns. One flag, behavioral detection passes
|
||||
- **0.9 reCAPTCHA v3 score** — human-level, server-verified
|
||||
- **Pro: 0.9 reCAPTCHA v3 score** — human-level, server-verified
|
||||
- **Passes Cloudflare Turnstile**, FingerprintJS, BrowserScan — tested against 30+ detection sites
|
||||
- **Auto-updating binary** — background update checks, always on the latest stealth build
|
||||
- **Auto-downloads the right binary** — free or Pro based on your license
|
||||
- **`pip install cloakbrowser`** or **`npm install cloakbrowser`** — binary auto-downloads, zero config
|
||||
- **Free and open source** — no subscriptions, no usage limits
|
||||
- **Open-source wrappers** — free v146 binary, Pro for latest builds
|
||||
|
||||
**Try it now** — no install needed:
|
||||
|
||||
@@ -150,7 +150,7 @@ page.goto("https://example.com")
|
||||
|
||||
---
|
||||
|
||||
## Latest: v0.4.6 — 59 source-level stealth patches, now on every platform (Chromium 148.0.7778.215.3)
|
||||
## Latest: v0.4.7 — 59 source-level stealth patches, now on every platform (Chromium 148.0.7778.215.3)
|
||||
|
||||
- **CloakBrowser Pro** — the latest binary (Chromium 148.0.7778.215.3, 59 source-level patches) is available to Pro subscribers, now on **all platforms including macOS** (Apple Silicon + Intel); v146 stays free forever. Set a `license_key` (`licenseKey` in JS) or the `CLOAKBROWSER_LICENSE_KEY` env var and the wrapper fetches the latest build automatically. See [CloakBrowser Pro](#cloakbrowser-pro)
|
||||
- **.NET 8 / C# client** — CloakBrowser now ships as a NuGet package (`CloakBrowser`), mirroring the Python and JS wrappers.
|
||||
@@ -206,15 +206,15 @@ Pro plans → **[cloakbrowser.dev](https://cloakbrowser.dev)**
|
||||
|
||||
## Test Results
|
||||
|
||||
All tests verified against live detection services. Last tested: Jun 2026 (Chromium 148).
|
||||
All tests verified against live detection services. Results below are for the latest Pro/current build unless noted. Last tested: Jul 2026 (Chromium 148).
|
||||
|
||||
| Detection Service | Stock Playwright | CloakBrowser | Notes |
|
||||
|---|---|---|---|
|
||||
| **reCAPTCHA v3** | 0.1 (bot) | **0.9** (human) | Server-side verified |
|
||||
| **reCAPTCHA v3** | 0.1 (bot) | **0.9** (human) | Pro/current build; server-side verified |
|
||||
| **Cloudflare Turnstile** (non-interactive) | FAIL | **PASS** | Auto-resolve |
|
||||
| **Cloudflare Turnstile** (managed) | FAIL | **PASS** | Single click |
|
||||
| **ShieldSquare** | BLOCKED | **PASS** | Production site |
|
||||
| **FingerprintJS** bot detection | DETECTED | **PASS** | demo.fingerprint.com |
|
||||
| **FingerprintJS** bot detection | DETECTED | **PASS** | Pro/current build; demo.fingerprint.com |
|
||||
| **BrowserScan** bot detection | DETECTED | **NORMAL** (4/4) | browserscan.net |
|
||||
| **bot.incolumitas.com** | 13 fails | **1 fail** | WEBDRIVER spec only |
|
||||
| **deviceandbrowserinfo.com** | 6 true flags | **0 true flags** | `isBot: false` |
|
||||
@@ -230,7 +230,7 @@ All tests verified against live detection services. Last tested: Jun 2026 (Chrom
|
||||
|
||||
<p align="center">
|
||||
<img src="https://i.imgur.com/hvIQyMv.png" width="600" alt="reCAPTCHA v3 — Score 0.9">
|
||||
<br><em>reCAPTCHA v3 score 0.9 — server-side verified (human-level)</em>
|
||||
<br><em>Pro/latest build: reCAPTCHA v3 score 0.9 — server-side verified (human-level)</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -245,7 +245,7 @@ All tests verified against live detection services. Last tested: Jun 2026 (Chrom
|
||||
|
||||
<p align="center">
|
||||
<img src="https://i.imgur.com/9n2C7tu.png" width="600" alt="FingerprintJS — Passed">
|
||||
<br><em>FingerprintJS web-scraping demo — data served, not blocked</em>
|
||||
<br><em>Pro/latest build: FingerprintJS web-scraping demo — data served, not blocked</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -257,7 +257,7 @@ All tests verified against live detection services. Last tested: Jun 2026 (Chrom
|
||||
|
||||
| Feature | Playwright | playwright-stealth | undetected-chromedriver | Camoufox | CloakBrowser |
|
||||
|---|---|---|---|---|---|
|
||||
| reCAPTCHA v3 score | 0.1 | 0.3-0.5 | 0.3-0.7 | 0.7-0.9 | **0.9** |
|
||||
| reCAPTCHA v3 score (Pro/current) | 0.1 | 0.3-0.5 | 0.3-0.7 | 0.7-0.9 | **0.9** |
|
||||
| Cloudflare Turnstile | Fail | Sometimes | Sometimes | Pass | **Pass** |
|
||||
| Patch level | None | JS injection | Config patches | C++ (Firefox) | **C++ (Chromium)** |
|
||||
| Survives Chrome updates | N/A | Breaks often | Breaks often | Yes | **Yes** |
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.4.6"
|
||||
__version__ = "0.4.7"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<!-- NuGet packaging metadata -->
|
||||
<PackageId>CloakBrowser</PackageId>
|
||||
<Version>0.4.6</Version>
|
||||
<Version>0.4.7</Version>
|
||||
<Authors>CloakHQ</Authors>
|
||||
<Description>Stealth Chromium that passes every bot detection test. Drop-in Playwright (.NET) replacement with source-level fingerprint patches.</Description>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
@@ -4,5 +4,5 @@ namespace CloakBrowser;
|
||||
public static class CloakVersion
|
||||
{
|
||||
/// <summary>The CloakBrowser .NET wrapper version.</summary>
|
||||
public const string Version = "0.4.6";
|
||||
public const string Version = "0.4.7";
|
||||
}
|
||||
|
||||
+78
-72
@@ -10,7 +10,6 @@ Usage:
|
||||
python examples/stealth_test.py --proxy http://10.50.96.5:8888
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
@@ -54,8 +53,8 @@ def test_bot_incolumitas(page):
|
||||
"""bot.incolumitas.com — comprehensive 30+ check bot detection."""
|
||||
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
|
||||
|
||||
# Poll until test count stabilizes (site runs tests progressively)
|
||||
last_total = 0
|
||||
results = {"passed": 0, "failed": 0, "failedTests": [], "total": 0}
|
||||
for _ in range(15):
|
||||
time.sleep(2)
|
||||
results = page.evaluate("""() => {
|
||||
@@ -63,12 +62,7 @@ def test_bot_incolumitas(page):
|
||||
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 {passed: okMatches.length, failed: failMatches.length, failedTests, total: okMatches.length + failMatches.length};
|
||||
}""")
|
||||
if results["total"] >= 30 and results["total"] == last_total:
|
||||
break
|
||||
@@ -77,6 +71,22 @@ def test_bot_incolumitas(page):
|
||||
return results
|
||||
|
||||
|
||||
def test_rebrowser(page):
|
||||
"""bot-detector.rebrowser.net — automation signal detector."""
|
||||
page.goto("https://bot-detector.rebrowser.net/", wait_until="networkidle", timeout=30000)
|
||||
time.sleep(10)
|
||||
|
||||
return page.evaluate("""() => {
|
||||
const el = document.getElementById('detections-json');
|
||||
if (!el) return {failing: [], totalFails: 0, passed: 0, notTriggered: 0, error: 'no detections-json element'};
|
||||
const tests = JSON.parse(el.value);
|
||||
const failing = tests.filter(t => t.rating === 1).map(t => t.type);
|
||||
const passed = tests.filter(t => t.rating === -1).length;
|
||||
const notTriggered = tests.filter(t => t.rating === 0).length;
|
||||
return {failing, totalFails: failing.length, passed, notTriggered, total: tests.length};
|
||||
}""")
|
||||
|
||||
|
||||
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)
|
||||
@@ -123,47 +133,17 @@ def test_deviceandbrowserinfo(page):
|
||||
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="domcontentloaded", timeout=30000)
|
||||
time.sleep(8)
|
||||
def test_creepjs_noise_off(page):
|
||||
"""CreepJS — lie detection with fingerprint noise disabled."""
|
||||
page.goto("https://abrahamjuliot.github.io/creepjs/", wait_until="domcontentloaded", timeout=30000)
|
||||
time.sleep(15)
|
||||
|
||||
# Click search to trigger bot detection — bots get blocked, humans see flights
|
||||
try:
|
||||
page.click("button:has-text('Search')", timeout=5000)
|
||||
time.sleep(5)
|
||||
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 page.evaluate("""() => {
|
||||
const fp = window.Fingerprint;
|
||||
if (!fp) return {error: 'Fingerprint not ready', totalLies: null};
|
||||
const lies = fp.lies || {};
|
||||
return {totalLies: lies.totalLies || 0};
|
||||
}""")
|
||||
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="domcontentloaded",
|
||||
timeout=30000,
|
||||
)
|
||||
# Wait for score to appear (polls up to 30s)
|
||||
for _ in range(15):
|
||||
time.sleep(2)
|
||||
score = page.evaluate("""() => {
|
||||
const text = document.body.innerText;
|
||||
const match = text.match(/"score":\\s*(\\d+\\.\\d+)/);
|
||||
return match ? parseFloat(match[1]) : null;
|
||||
}""")
|
||||
if score is not None:
|
||||
break
|
||||
|
||||
return {"score": score}
|
||||
|
||||
|
||||
TESTS = [
|
||||
@@ -180,11 +160,33 @@ TESTS = [
|
||||
"url": "https://bot.incolumitas.com",
|
||||
"runner": test_bot_incolumitas,
|
||||
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||
+ (" — ALL GREEN" if r.get("failed", 0) == 0
|
||||
+ (
|
||||
" — ALL GREEN"
|
||||
if r.get("failed", 0) == 0
|
||||
else f" (FAILED: {', '.join(r.get('failedTests', []))} — known false positives)"
|
||||
if set(r.get("failedTests", [])) <= {"WEBDRIVER", "connectionRTT"}
|
||||
else f" (FAILED: {', '.join(r.get('failedTests', []))})"),
|
||||
"pass": lambda r: set(r.get("failedTests", [])) <= {"WEBDRIVER", "connectionRTT"}, # known false positives
|
||||
else f" (FAILED: {', '.join(r.get('failedTests', []))})"
|
||||
),
|
||||
"pass": lambda r: set(r.get("failedTests", [])) <= {"WEBDRIVER", "connectionRTT"},
|
||||
},
|
||||
{
|
||||
"name": "Rebrowser Bot Detector",
|
||||
"url": "https://bot-detector.rebrowser.net/",
|
||||
"runner": test_rebrowser,
|
||||
"verdict": lambda r: (
|
||||
f"🟢{r.get('passed', 0)} ⚪{r.get('notTriggered', 0)} — ALL CLEAN"
|
||||
if r.get("totalFails", 1) == 0
|
||||
else f"FAIL: {', '.join(r.get('failing', []))}"
|
||||
),
|
||||
"pass": lambda r: r.get("totalFails", 1) == 0,
|
||||
},
|
||||
{
|
||||
"name": "deviceandbrowserinfo.com",
|
||||
"url": "https://deviceandbrowserinfo.com/are_you_a_bot",
|
||||
"runner": test_deviceandbrowserinfo,
|
||||
"verdict": lambda r: f"isBot: {r.get('isBot', 'unknown')}"
|
||||
+ (f", trueFlags: {sum(1 for v in r.get('checks', {}).values() if v)}" if r.get("checks") else ""),
|
||||
"pass": lambda r: not r.get("isBot", True) and not any(r.get("checks", {}).values()),
|
||||
},
|
||||
{
|
||||
"name": "BrowserScan",
|
||||
@@ -194,26 +196,12 @@ TESTS = [
|
||||
"pass": lambda r: r.get("abnormal", 1) == 0,
|
||||
},
|
||||
{
|
||||
"name": "deviceandbrowserinfo.com",
|
||||
"url": "https://deviceandbrowserinfo.com/are_you_a_bot",
|
||||
"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",
|
||||
"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,
|
||||
"name": "CreepJS lies (noise=false)",
|
||||
"url": "https://abrahamjuliot.github.io/creepjs/",
|
||||
"runner": test_creepjs_noise_off,
|
||||
"verdict": lambda r: f"lies: {r.get('totalLies', 'N/A')}",
|
||||
"pass": lambda r: r.get("totalLies") == 0,
|
||||
"args": ["--fingerprint-noise=false"],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -229,6 +217,7 @@ def main():
|
||||
print("Launching stealth browser...", flush=True)
|
||||
|
||||
browser = launch(headless=not HEADED, proxy=PROXY, geoip=True)
|
||||
shared_browser_closed = False
|
||||
page = browser.new_page()
|
||||
|
||||
# Show browser fingerprint details
|
||||
@@ -281,8 +270,19 @@ def main():
|
||||
print(f"--- {name} ---")
|
||||
print(f"URL: {test['url']}")
|
||||
|
||||
test_browser = browser
|
||||
test_context = None
|
||||
test_page = page
|
||||
try:
|
||||
result = test["runner"](page)
|
||||
if test.get("args"):
|
||||
browser.close()
|
||||
shared_browser_closed = True
|
||||
test_browser = launch(headless=not HEADED, proxy=PROXY, geoip=True, args=test["args"])
|
||||
|
||||
test_context = test_browser.new_context(viewport={"width": 1920, "height": 1080})
|
||||
test_page = test_context.new_page()
|
||||
|
||||
result = test["runner"](test_page)
|
||||
passed = test["pass"](result)
|
||||
verdict = test["verdict"](result)
|
||||
status = "PASS" if passed else "FAIL"
|
||||
@@ -292,15 +292,21 @@ def main():
|
||||
|
||||
if SCREENSHOTS:
|
||||
filename = f"stealth_test_{name.replace('.', '_').replace(' ', '_').replace('/', '_')}.png"
|
||||
page.screenshot(path=filename)
|
||||
test_page.screenshot(path=filename)
|
||||
print(f"Screenshot: {filename}")
|
||||
|
||||
except Exception as e:
|
||||
results_summary.append((name, "ERROR", str(e)))
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
if test_context is not None:
|
||||
test_context.close()
|
||||
if test_browser is not browser:
|
||||
test_browser.close()
|
||||
|
||||
print()
|
||||
|
||||
if not shared_browser_closed:
|
||||
browser.close()
|
||||
|
||||
# Summary table
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tar": "^7.0.0"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user