mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
fix: code review — breaking change docs, version marker migration, checksum tests
- Document playwright→patchright breaking change in CHANGELOG - Document default viewport change (1920x955) in CHANGELOG - Add legacy latest_version marker fallback for <0.3.0 upgrades - Add checksum parsing/verification tests (Python + JS) - Document CLOAKBROWSER_SKIP_CHECKSUM env var in README - Fix case-insensitive SHA256SUMS regex in JS - Replace page.wait_for_timeout() with time.sleep() in example
This commit is contained in:
@@ -10,6 +10,11 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
|||||||
|
|
||||||
Chromium v145 upgrade. 26 fingerprint patches (up from 16). New download verification and fallback system. Pending: macOS v145 binary builds.
|
Chromium v145 upgrade. 26 fingerprint patches (up from 16). New download verification and fallback system. Pending: macOS v145 binary builds.
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
- **[wrapper]** Python dependency changed from `playwright` to `patchright` (CDP stealth fork). Patchright is API-compatible, but if you import `playwright` directly elsewhere, add it as a separate dependency. Replace `from playwright.sync_api` with `from patchright.sync_api` (or keep using `cloakbrowser.launch()` which handles this automatically).
|
||||||
|
- **[wrapper]** `launch_context()` / `launchContext()` now defaults viewport to 1920x955 (realistic maximized Chrome on 1080p Windows) instead of Playwright's default 1280x720. Pass `viewport={"width": 1280, "height": 720}` explicitly to restore old behavior.
|
||||||
|
|
||||||
### 2026-03-01
|
### 2026-03-01
|
||||||
|
|
||||||
- **[wrapper]** Upgrade wrapper to Chromium v145.0.7632.109
|
- **[wrapper]** Upgrade wrapper to Chromium v145.0.7632.109
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ clearCache();
|
|||||||
| `CLOAKBROWSER_CACHE_DIR` | `~/.cloakbrowser` | Binary cache directory |
|
| `CLOAKBROWSER_CACHE_DIR` | `~/.cloakbrowser` | Binary cache directory |
|
||||||
| `CLOAKBROWSER_DOWNLOAD_URL` | `cloakbrowser.dev` | Custom download URL for binary |
|
| `CLOAKBROWSER_DOWNLOAD_URL` | `cloakbrowser.dev` | Custom download URL for binary |
|
||||||
| `CLOAKBROWSER_AUTO_UPDATE` | `true` | Set to `false` to disable background update checks |
|
| `CLOAKBROWSER_AUTO_UPDATE` | `true` | Set to `false` to disable background update checks |
|
||||||
|
| `CLOAKBROWSER_SKIP_CHECKSUM` | `false` | Set to `true` to skip SHA-256 verification after download |
|
||||||
|
|
||||||
## Fingerprint Management
|
## Fingerprint Management
|
||||||
|
|
||||||
|
|||||||
+13
-11
@@ -162,17 +162,19 @@ def get_effective_version() -> str:
|
|||||||
Returns the platform's hardcoded version if no update has been downloaded.
|
Returns the platform's hardcoded version if no update has been downloaded.
|
||||||
"""
|
"""
|
||||||
base = get_chromium_version()
|
base = get_chromium_version()
|
||||||
marker = get_cache_dir() / f"latest_version_{get_platform_tag()}"
|
# Try platform-scoped marker first, fall back to legacy marker for upgrades from <0.3.0
|
||||||
if marker.exists():
|
cache = get_cache_dir()
|
||||||
try:
|
for name in (f"latest_version_{get_platform_tag()}", "latest_version"):
|
||||||
version = marker.read_text().strip()
|
marker = cache / name
|
||||||
if version and _version_newer(version, base):
|
if marker.exists():
|
||||||
# Verify the binary actually exists
|
try:
|
||||||
binary = get_binary_path(version)
|
version = marker.read_text().strip()
|
||||||
if binary.exists():
|
if version and _version_newer(version, base):
|
||||||
return version
|
binary = get_binary_path(version)
|
||||||
except (ValueError, OSError):
|
if binary.exists():
|
||||||
pass
|
return version
|
||||||
|
except (ValueError, OSError):
|
||||||
|
pass
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
from cloakbrowser import launch_context
|
from cloakbrowser import launch_context
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ def test_fingerprint_scan(page):
|
|||||||
"""fingerprint-scan.com — bot risk score + headless detection signals."""
|
"""fingerprint-scan.com — bot risk score + headless detection signals."""
|
||||||
print("=== fingerprint-scan.com ===")
|
print("=== fingerprint-scan.com ===")
|
||||||
page.goto("https://fingerprint-scan.com/", wait_until="domcontentloaded", timeout=30000)
|
page.goto("https://fingerprint-scan.com/", wait_until="domcontentloaded", timeout=30000)
|
||||||
page.wait_for_timeout(20000) # Castle.js needs time to compute score
|
time.sleep(20) # Castle.js needs time to compute score
|
||||||
|
|
||||||
# Check bot risk score
|
# Check bot risk score
|
||||||
score = page.evaluate(
|
score = page.evaluate(
|
||||||
@@ -92,7 +93,7 @@ def test_creepjs(page):
|
|||||||
"https://abrahamjuliot.github.io/creepjs/", wait_until="domcontentloaded", timeout=30000
|
"https://abrahamjuliot.github.io/creepjs/", wait_until="domcontentloaded", timeout=30000
|
||||||
)
|
)
|
||||||
print("Waiting 30s for CreepJS analysis...")
|
print("Waiting 30s for CreepJS analysis...")
|
||||||
page.wait_for_timeout(30000)
|
time.sleep(30)
|
||||||
|
|
||||||
# Extract % scores from page text (matches test-infra/matrix_tests/group3_bot_detection.py)
|
# Extract % scores from page text (matches test-infra/matrix_tests/group3_bot_detection.py)
|
||||||
scores = page.evaluate("""() => {
|
scores = page.evaluate("""() => {
|
||||||
|
|||||||
+14
-10
@@ -122,19 +122,23 @@ export function getFallbackDownloadUrl(version?: string): string {
|
|||||||
|
|
||||||
export function getEffectiveVersion(): string {
|
export function getEffectiveVersion(): string {
|
||||||
const base = getChromiumVersion();
|
const base = getChromiumVersion();
|
||||||
const marker = path.join(getCacheDir(), `latest_version_${getPlatformTag()}`);
|
const cacheDir = getCacheDir();
|
||||||
try {
|
// Try platform-scoped marker first, fall back to legacy marker for upgrades from <0.3.0
|
||||||
if (fs.existsSync(marker)) {
|
for (const name of [`latest_version_${getPlatformTag()}`, "latest_version"]) {
|
||||||
const version = fs.readFileSync(marker, "utf-8").trim();
|
const marker = path.join(cacheDir, name);
|
||||||
if (version && versionNewer(version, base)) {
|
try {
|
||||||
const binary = getBinaryPath(version);
|
if (fs.existsSync(marker)) {
|
||||||
if (fs.existsSync(binary)) {
|
const version = fs.readFileSync(marker, "utf-8").trim();
|
||||||
return version;
|
if (version && versionNewer(version, base)) {
|
||||||
|
const binary = getBinaryPath(version);
|
||||||
|
if (fs.existsSync(binary)) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Marker unreadable — try next
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// Marker unreadable — fall back to hardcoded
|
|
||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -234,12 +234,13 @@ async function fetchChecksums(version?: string): Promise<Map<string, string> | n
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseChecksums(text: string): Map<string, string> {
|
/** @internal Exported for testing only. */
|
||||||
|
export function parseChecksums(text: string): Map<string, string> {
|
||||||
const result = new Map<string, string>();
|
const result = new Map<string, string>();
|
||||||
for (const line of text.trim().split("\n")) {
|
for (const line of text.trim().split("\n")) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed) continue;
|
if (!trimmed) continue;
|
||||||
const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/);
|
const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
|
||||||
if (match) {
|
if (match) {
|
||||||
result.set(match[2]!, match[1]!.toLowerCase());
|
result.set(match[2]!, match[1]!.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-1
@@ -8,7 +8,7 @@ import {
|
|||||||
parseVersion,
|
parseVersion,
|
||||||
versionNewer,
|
versionNewer,
|
||||||
} from "../src/config.js";
|
} from "../src/config.js";
|
||||||
import { getLatestChromiumVersion } from "../src/download.js";
|
import { getLatestChromiumVersion, parseChecksums } from "../src/download.js";
|
||||||
|
|
||||||
describe("version comparison", () => {
|
describe("version comparison", () => {
|
||||||
it("parseVersion handles 4-part versions", () => {
|
it("parseVersion handles 4-part versions", () => {
|
||||||
@@ -149,6 +149,38 @@ describe("latest version (platform-aware)", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("parseChecksums", () => {
|
||||||
|
// Valid 64-char hex strings for testing
|
||||||
|
const HASH_A = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||||
|
const HASH_B = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
|
||||||
|
|
||||||
|
it("parses standard SHA256SUMS format", () => {
|
||||||
|
const text = [
|
||||||
|
`${HASH_A} cloakbrowser-linux-x64.tar.gz`,
|
||||||
|
`${HASH_B} cloakbrowser-darwin-arm64.tar.gz`,
|
||||||
|
].join("\n");
|
||||||
|
const result = parseChecksums(text);
|
||||||
|
expect(result.get("cloakbrowser-linux-x64.tar.gz")).toBe(HASH_A);
|
||||||
|
expect(result.get("cloakbrowser-darwin-arm64.tar.gz")).toBe(HASH_B);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles binary-mode asterisk prefix", () => {
|
||||||
|
const text = `${HASH_A} *cloakbrowser-linux-x64.tar.gz`;
|
||||||
|
const result = parseChecksums(text);
|
||||||
|
expect(result.has("cloakbrowser-linux-x64.tar.gz")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips empty lines", () => {
|
||||||
|
const text = `\n\n${HASH_A} file.tar.gz\n\n`;
|
||||||
|
expect(parseChecksums(text).size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty map for empty input", () => {
|
||||||
|
expect(parseChecksums("").size).toBe(0);
|
||||||
|
expect(parseChecksums(" \n \n").size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("effective version", () => {
|
describe("effective version", () => {
|
||||||
it("returns platform version when no marker exists", () => {
|
it("returns platform version when no marker exists", () => {
|
||||||
// Default behavior — no marker file in test environment
|
// Default behavior — no marker file in test environment
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
@@ -19,7 +20,9 @@ from cloakbrowser.config import (
|
|||||||
)
|
)
|
||||||
from cloakbrowser.download import (
|
from cloakbrowser.download import (
|
||||||
_get_latest_chromium_version,
|
_get_latest_chromium_version,
|
||||||
|
_parse_checksums,
|
||||||
_should_check_for_update,
|
_should_check_for_update,
|
||||||
|
_verify_checksum,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -242,3 +245,52 @@ class TestGetLatestVersion:
|
|||||||
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
||||||
result = _get_latest_chromium_version()
|
result = _get_latest_chromium_version()
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseChecksums:
|
||||||
|
HASH_A = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
HASH_B = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
||||||
|
|
||||||
|
def test_standard_format(self):
|
||||||
|
text = (
|
||||||
|
f"{self.HASH_A} cloakbrowser-linux-x64.tar.gz\n"
|
||||||
|
f"{self.HASH_B} cloakbrowser-darwin-arm64.tar.gz\n"
|
||||||
|
)
|
||||||
|
result = _parse_checksums(text)
|
||||||
|
assert result["cloakbrowser-linux-x64.tar.gz"] == self.HASH_A
|
||||||
|
assert result["cloakbrowser-darwin-arm64.tar.gz"] == self.HASH_B
|
||||||
|
|
||||||
|
def test_binary_mode_asterisk(self):
|
||||||
|
text = f"{self.HASH_A} *cloakbrowser-linux-x64.tar.gz\n"
|
||||||
|
result = _parse_checksums(text)
|
||||||
|
assert "cloakbrowser-linux-x64.tar.gz" in result
|
||||||
|
|
||||||
|
def test_empty_lines_skipped(self):
|
||||||
|
text = f"\n\n{self.HASH_A} file.tar.gz\n\n"
|
||||||
|
result = _parse_checksums(text)
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
def test_uppercase_lowered(self):
|
||||||
|
text = f"{self.HASH_A.upper()} file.tar.gz\n"
|
||||||
|
result = _parse_checksums(text)
|
||||||
|
assert result["file.tar.gz"] == self.HASH_A
|
||||||
|
|
||||||
|
def test_empty_input(self):
|
||||||
|
assert _parse_checksums("") == {}
|
||||||
|
assert _parse_checksums(" \n \n") == {}
|
||||||
|
|
||||||
|
|
||||||
|
class TestVerifyChecksum:
|
||||||
|
def test_matching_checksum(self, tmp_path):
|
||||||
|
content = b"test binary content"
|
||||||
|
file = tmp_path / "test.tar.gz"
|
||||||
|
file.write_bytes(content)
|
||||||
|
expected = hashlib.sha256(content).hexdigest()
|
||||||
|
# Should not raise
|
||||||
|
_verify_checksum(file, expected)
|
||||||
|
|
||||||
|
def test_mismatched_checksum(self, tmp_path):
|
||||||
|
file = tmp_path / "test.tar.gz"
|
||||||
|
file.write_bytes(b"real content")
|
||||||
|
with pytest.raises(RuntimeError, match="Checksum verification failed"):
|
||||||
|
_verify_checksum(file, "0" * 64)
|
||||||
|
|||||||
Reference in New Issue
Block a user