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.
|
||||
|
||||
### 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
|
||||
|
||||
- **[wrapper]** Upgrade wrapper to Chromium v145.0.7632.109
|
||||
|
||||
@@ -324,6 +324,7 @@ clearCache();
|
||||
| `CLOAKBROWSER_CACHE_DIR` | `~/.cloakbrowser` | Binary cache directory |
|
||||
| `CLOAKBROWSER_DOWNLOAD_URL` | `cloakbrowser.dev` | Custom download URL for binary |
|
||||
| `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
|
||||
|
||||
|
||||
+13
-11
@@ -162,17 +162,19 @@ def get_effective_version() -> str:
|
||||
Returns the platform's hardcoded version if no update has been downloaded.
|
||||
"""
|
||||
base = get_chromium_version()
|
||||
marker = get_cache_dir() / f"latest_version_{get_platform_tag()}"
|
||||
if marker.exists():
|
||||
try:
|
||||
version = marker.read_text().strip()
|
||||
if version and _version_newer(version, base):
|
||||
# Verify the binary actually exists
|
||||
binary = get_binary_path(version)
|
||||
if binary.exists():
|
||||
return version
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
# Try platform-scoped marker first, fall back to legacy marker for upgrades from <0.3.0
|
||||
cache = get_cache_dir()
|
||||
for name in (f"latest_version_{get_platform_tag()}", "latest_version"):
|
||||
marker = cache / name
|
||||
if marker.exists():
|
||||
try:
|
||||
version = marker.read_text().strip()
|
||||
if version and _version_newer(version, base):
|
||||
binary = get_binary_path(version)
|
||||
if binary.exists():
|
||||
return version
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
return base
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Usage:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
from cloakbrowser import launch_context
|
||||
|
||||
@@ -27,7 +28,7 @@ def test_fingerprint_scan(page):
|
||||
"""fingerprint-scan.com — bot risk score + headless detection signals."""
|
||||
print("=== fingerprint-scan.com ===")
|
||||
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
|
||||
score = page.evaluate(
|
||||
@@ -92,7 +93,7 @@ def test_creepjs(page):
|
||||
"https://abrahamjuliot.github.io/creepjs/", wait_until="domcontentloaded", timeout=30000
|
||||
)
|
||||
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)
|
||||
scores = page.evaluate("""() => {
|
||||
|
||||
+14
-10
@@ -122,19 +122,23 @@ export function getFallbackDownloadUrl(version?: string): string {
|
||||
|
||||
export function getEffectiveVersion(): string {
|
||||
const base = getChromiumVersion();
|
||||
const marker = path.join(getCacheDir(), `latest_version_${getPlatformTag()}`);
|
||||
try {
|
||||
if (fs.existsSync(marker)) {
|
||||
const version = fs.readFileSync(marker, "utf-8").trim();
|
||||
if (version && versionNewer(version, base)) {
|
||||
const binary = getBinaryPath(version);
|
||||
if (fs.existsSync(binary)) {
|
||||
return version;
|
||||
const cacheDir = getCacheDir();
|
||||
// Try platform-scoped marker first, fall back to legacy marker for upgrades from <0.3.0
|
||||
for (const name of [`latest_version_${getPlatformTag()}`, "latest_version"]) {
|
||||
const marker = path.join(cacheDir, name);
|
||||
try {
|
||||
if (fs.existsSync(marker)) {
|
||||
const version = fs.readFileSync(marker, "utf-8").trim();
|
||||
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;
|
||||
}
|
||||
|
||||
+3
-2
@@ -234,12 +234,13 @@ async function fetchChecksums(version?: string): Promise<Map<string, string> | n
|
||||
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>();
|
||||
for (const line of text.trim().split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/);
|
||||
const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
|
||||
if (match) {
|
||||
result.set(match[2]!, match[1]!.toLowerCase());
|
||||
}
|
||||
|
||||
+33
-1
@@ -8,7 +8,7 @@ import {
|
||||
parseVersion,
|
||||
versionNewer,
|
||||
} from "../src/config.js";
|
||||
import { getLatestChromiumVersion } from "../src/download.js";
|
||||
import { getLatestChromiumVersion, parseChecksums } from "../src/download.js";
|
||||
|
||||
describe("version comparison", () => {
|
||||
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", () => {
|
||||
it("returns platform version when no marker exists", () => {
|
||||
// Default behavior — no marker file in test environment
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -19,7 +20,9 @@ from cloakbrowser.config import (
|
||||
)
|
||||
from cloakbrowser.download import (
|
||||
_get_latest_chromium_version,
|
||||
_parse_checksums,
|
||||
_should_check_for_update,
|
||||
_verify_checksum,
|
||||
)
|
||||
|
||||
|
||||
@@ -242,3 +245,52 @@ class TestGetLatestVersion:
|
||||
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
||||
result = _get_latest_chromium_version()
|
||||
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