mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
fix: deduplicate CLI flags when user args overlap with stealth defaults
Bump version to 0.3.9. Extract shared buildArgs into js/src/args.ts (DRY), guard console.debug behind DEBUG=cloakbrowser env var, strengthen caplog assertion.
This commit is contained in:
+3
-4
@@ -8,12 +8,11 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
|
||||
## [0.3.9] — 2026-03-05
|
||||
|
||||
- **[wrapper]** Default Playwright backend switched from `patchright` to stock `playwright`. Patchright broke proxy auth and `add_init_script` (#27) and is redundant since the binary handles stealth at C++ level. Opt in with `launch(backend="patchright")` or `CLOAKBROWSER_BACKEND=patchright` env var. Install: `pip install cloakbrowser[patchright]`
|
||||
|
||||
## [0.3.8] — 2026-03-05
|
||||
|
||||
- **[binary]** Upgrade Chromium base to 145.0.7632.159 (Linux x64). macOS and Windows remain on 145.0.7632.109.2
|
||||
- **[binary]** WebGPU adapter spoofing for headless/Docker, timezone multi-context fix, stealth audit phase 2 (6 detection vector fixes), font auto-hide for cross-platform fingerprints
|
||||
- **[wrapper]** Default Playwright backend switched from `patchright` to stock `playwright`. Patchright broke proxy auth and `add_init_script` (#27) and is redundant since the binary handles stealth at C++ level. Opt in with `launch(backend="patchright")` or `CLOAKBROWSER_BACKEND=patchright` env var. Install: `pip install cloakbrowser[patchright]`
|
||||
- **[wrapper]** Deduplicate CLI flags when user args overlap with stealth defaults — user values win cleanly instead of passing both to Chromium
|
||||
- **[wrapper]** Extract shared `buildArgs` into `js/src/args.ts` (JS DRY fix), guard debug logging behind `DEBUG=cloakbrowser` env var
|
||||
|
||||
## [0.3.7] — 2026-03-05
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.8"
|
||||
__version__ = "0.3.9"
|
||||
|
||||
+28
-7
@@ -549,18 +549,39 @@ def _build_args(
|
||||
timezone: str | None = None,
|
||||
locale: str | None = None,
|
||||
) -> list[str]:
|
||||
"""Combine stealth args with user-provided args and locale flags."""
|
||||
result = []
|
||||
"""Combine stealth args with user-provided args and locale flags.
|
||||
|
||||
Deduplicates by flag key (everything before '=').
|
||||
Priority: stealth defaults < user args < dedicated params (timezone/locale).
|
||||
"""
|
||||
seen: dict[str, str] = {}
|
||||
|
||||
if stealth_args:
|
||||
result.extend(get_default_stealth_args())
|
||||
for arg in get_default_stealth_args():
|
||||
seen[arg.split("=", 1)[0]] = arg
|
||||
|
||||
if extra_args:
|
||||
result.extend(extra_args)
|
||||
for arg in extra_args:
|
||||
key = arg.split("=", 1)[0]
|
||||
if key in seen:
|
||||
logger.debug("Arg override: %s -> %s", seen[key], arg)
|
||||
seen[key] = arg
|
||||
|
||||
# Timezone/locale flags are independent of stealth_args — always inject when set
|
||||
if timezone:
|
||||
result.append(f"--fingerprint-timezone={timezone}")
|
||||
key = "--fingerprint-timezone"
|
||||
flag = f"{key}={timezone}"
|
||||
if key in seen:
|
||||
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
||||
seen[key] = flag
|
||||
if locale:
|
||||
result.append(f"--lang={locale}")
|
||||
return result
|
||||
key = "--lang"
|
||||
flag = f"{key}={locale}"
|
||||
if key in seen:
|
||||
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
||||
seen[key] = flag
|
||||
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _parse_proxy_url(proxy: str) -> dict[str, Any]:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.3.8",
|
||||
"version": "0.3.9",
|
||||
"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",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Shared argument builder for Playwright and Puppeteer wrappers.
|
||||
*/
|
||||
|
||||
import type { LaunchOptions } from "./types.js";
|
||||
import { getDefaultStealthArgs } from "./config.js";
|
||||
|
||||
const DEBUG = /\bcloakbrowser\b/.test(process.env.DEBUG ?? "");
|
||||
|
||||
/**
|
||||
* Build deduplicated Chromium CLI args from stealth defaults + user overrides.
|
||||
*
|
||||
* Priority: stealth defaults < user args < dedicated params (timezone/locale).
|
||||
*/
|
||||
export function buildArgs(options: LaunchOptions): string[] {
|
||||
const seen = new Map<string, string>();
|
||||
|
||||
if (options.stealthArgs !== false) {
|
||||
for (const arg of getDefaultStealthArgs()) {
|
||||
seen.set(arg.split("=")[0], arg);
|
||||
}
|
||||
}
|
||||
if (options.args) {
|
||||
for (const arg of options.args) {
|
||||
const key = arg.split("=")[0];
|
||||
if (seen.has(key)) {
|
||||
if (DEBUG) console.debug(`[cloakbrowser] Arg override: ${seen.get(key)} -> ${arg}`);
|
||||
}
|
||||
seen.set(key, arg);
|
||||
}
|
||||
}
|
||||
if (options.timezone) {
|
||||
const key = "--fingerprint-timezone";
|
||||
const flag = `${key}=${options.timezone}`;
|
||||
if (seen.has(key)) {
|
||||
if (DEBUG) console.debug(`[cloakbrowser] Arg override: ${seen.get(key)} -> ${flag}`);
|
||||
}
|
||||
seen.set(key, flag);
|
||||
}
|
||||
if (options.locale) {
|
||||
const key = "--lang";
|
||||
const flag = `${key}=${options.locale}`;
|
||||
if (seen.has(key)) {
|
||||
if (DEBUG) console.debug(`[cloakbrowser] Arg override: ${seen.get(key)} -> ${flag}`);
|
||||
}
|
||||
seen.set(key, flag);
|
||||
}
|
||||
return [...seen.values()];
|
||||
}
|
||||
+3
-22
@@ -5,7 +5,8 @@
|
||||
|
||||
import type { Browser, BrowserContext } from "playwright-core";
|
||||
import type { LaunchOptions, LaunchContextOptions, LaunchPersistentContextOptions } from "./types.js";
|
||||
import { DEFAULT_VIEWPORT, getDefaultStealthArgs } from "./config.js";
|
||||
import { DEFAULT_VIEWPORT } from "./config.js";
|
||||
import { buildArgs } from "./args.js";
|
||||
import { ensureBinary } from "./download.js";
|
||||
import { parseProxyUrl } from "./proxy.js";
|
||||
|
||||
@@ -176,24 +177,4 @@ async function maybeResolveGeoip(
|
||||
}
|
||||
|
||||
/** @internal Exposed for unit tests only. */
|
||||
export function _buildArgsForTest(options: LaunchOptions): string[] {
|
||||
return buildArgs(options);
|
||||
}
|
||||
|
||||
function buildArgs(options: LaunchOptions): string[] {
|
||||
const args: string[] = [];
|
||||
if (options.stealthArgs !== false) {
|
||||
args.push(...getDefaultStealthArgs());
|
||||
}
|
||||
if (options.args) {
|
||||
args.push(...options.args);
|
||||
}
|
||||
// Timezone/locale flags — always inject when set
|
||||
if (options.timezone) {
|
||||
args.push(`--fingerprint-timezone=${options.timezone}`);
|
||||
}
|
||||
if (options.locale) {
|
||||
args.push(`--lang=${options.locale}`);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
export { buildArgs as _buildArgsForTest } from "./args.js";
|
||||
|
||||
+1
-17
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { Browser } from "puppeteer-core";
|
||||
import type { LaunchOptions } from "./types.js";
|
||||
import { getDefaultStealthArgs } from "./config.js";
|
||||
import { buildArgs } from "./args.js";
|
||||
import { ensureBinary } from "./download.js";
|
||||
import { parseProxyUrl } from "./proxy.js";
|
||||
|
||||
@@ -99,19 +99,3 @@ async function maybeResolveGeoip(
|
||||
};
|
||||
}
|
||||
|
||||
function buildArgs(options: LaunchOptions): string[] {
|
||||
const args: string[] = [];
|
||||
if (options.stealthArgs !== false) {
|
||||
args.push(...getDefaultStealthArgs());
|
||||
}
|
||||
if (options.args) {
|
||||
args.push(...options.args);
|
||||
}
|
||||
if (options.timezone) {
|
||||
args.push(`--fingerprint-timezone=${options.timezone}`);
|
||||
}
|
||||
if (options.locale) {
|
||||
args.push(`--lang=${options.locale}`);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,59 @@ describe("buildArgs timezone/locale", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildArgs deduplication", () => {
|
||||
it("user --fingerprint overrides default seed", () => {
|
||||
const args = _buildArgsForTest({ args: ["--fingerprint=99887"] });
|
||||
const fpArgs = args.filter(a => a.startsWith("--fingerprint="));
|
||||
expect(fpArgs).toHaveLength(1);
|
||||
expect(fpArgs[0]).toBe("--fingerprint=99887");
|
||||
});
|
||||
|
||||
it("user --fingerprint-platform overrides default", () => {
|
||||
const args = _buildArgsForTest({ args: ["--fingerprint-platform=linux"] });
|
||||
const platArgs = args.filter(a => a.startsWith("--fingerprint-platform="));
|
||||
expect(platArgs).toHaveLength(1);
|
||||
expect(platArgs[0]).toBe("--fingerprint-platform=linux");
|
||||
});
|
||||
|
||||
it("timezone param overrides user --fingerprint-timezone arg", () => {
|
||||
const args = _buildArgsForTest({
|
||||
args: ["--fingerprint-timezone=Europe/London"],
|
||||
timezone: "America/New_York",
|
||||
});
|
||||
const tzArgs = args.filter(a => a.startsWith("--fingerprint-timezone="));
|
||||
expect(tzArgs).toHaveLength(1);
|
||||
expect(tzArgs[0]).toBe("--fingerprint-timezone=America/New_York");
|
||||
});
|
||||
|
||||
it("locale param overrides user --lang arg", () => {
|
||||
const args = _buildArgsForTest({
|
||||
args: ["--lang=de-DE"],
|
||||
locale: "en-US",
|
||||
});
|
||||
const langArgs = args.filter(a => a.startsWith("--lang="));
|
||||
expect(langArgs).toHaveLength(1);
|
||||
expect(langArgs[0]).toBe("--lang=en-US");
|
||||
});
|
||||
|
||||
it("no duplicate flag keys in output", () => {
|
||||
const args = _buildArgsForTest({
|
||||
args: ["--fingerprint=99887", "--fingerprint-timezone=UTC", "--lang=fr-FR"],
|
||||
timezone: "Europe/Berlin",
|
||||
locale: "de-DE",
|
||||
});
|
||||
const keys = args.map(a => a.split("=")[0]);
|
||||
expect(new Set(keys).size).toBe(keys.length);
|
||||
});
|
||||
|
||||
it("non-value flags preserved without dedup issues", () => {
|
||||
const args = _buildArgsForTest({ args: ["--disable-gpu", "--no-zygote"] });
|
||||
expect(args).toContain("--disable-gpu");
|
||||
expect(args).toContain("--no-zygote");
|
||||
expect(args).toContain("--no-sandbox");
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrateTimezoneId deprecation", () => {
|
||||
it("migrates timezoneId to timezone", () => {
|
||||
const result = migrateTimezoneId({ timezoneId: "Europe/Paris" });
|
||||
|
||||
@@ -92,3 +92,75 @@ def test_migrate_both_none():
|
||||
result = _migrate_timezone_id(None, kwargs)
|
||||
assert result is None
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
# --- Deduplication tests ---
|
||||
|
||||
|
||||
def test_user_fingerprint_overrides_default():
|
||||
"""User --fingerprint should override the random default seed."""
|
||||
args = _build_args(stealth_args=True, extra_args=["--fingerprint=99887"])
|
||||
fingerprint_args = [a for a in args if a.startswith("--fingerprint=")]
|
||||
assert len(fingerprint_args) == 1
|
||||
assert fingerprint_args[0] == "--fingerprint=99887"
|
||||
|
||||
|
||||
def test_user_platform_overrides_default():
|
||||
"""User --fingerprint-platform should override the default."""
|
||||
args = _build_args(stealth_args=True, extra_args=["--fingerprint-platform=linux"])
|
||||
platform_args = [a for a in args if a.startswith("--fingerprint-platform=")]
|
||||
assert len(platform_args) == 1
|
||||
assert platform_args[0] == "--fingerprint-platform=linux"
|
||||
|
||||
|
||||
def test_timezone_param_overrides_user_arg():
|
||||
"""Dedicated timezone param should override user arg."""
|
||||
args = _build_args(
|
||||
stealth_args=True,
|
||||
extra_args=["--fingerprint-timezone=Europe/London"],
|
||||
timezone="America/New_York",
|
||||
)
|
||||
tz_args = [a for a in args if a.startswith("--fingerprint-timezone=")]
|
||||
assert len(tz_args) == 1
|
||||
assert tz_args[0] == "--fingerprint-timezone=America/New_York"
|
||||
|
||||
|
||||
def test_locale_param_overrides_user_arg():
|
||||
"""Dedicated locale param should override user --lang arg."""
|
||||
args = _build_args(
|
||||
stealth_args=True,
|
||||
extra_args=["--lang=de-DE"],
|
||||
locale="en-US",
|
||||
)
|
||||
lang_args = [a for a in args if a.startswith("--lang=")]
|
||||
assert len(lang_args) == 1
|
||||
assert lang_args[0] == "--lang=en-US"
|
||||
|
||||
|
||||
def test_no_duplicate_flags():
|
||||
"""No flag key should appear more than once in the output."""
|
||||
args = _build_args(
|
||||
stealth_args=True,
|
||||
extra_args=["--fingerprint=99887", "--fingerprint-timezone=UTC", "--lang=fr-FR"],
|
||||
timezone="Europe/Berlin",
|
||||
locale="de-DE",
|
||||
)
|
||||
keys = [a.split("=", 1)[0] for a in args]
|
||||
assert len(keys) == len(set(keys)), f"Duplicate keys found: {keys}"
|
||||
|
||||
|
||||
def test_non_value_flags_preserved():
|
||||
"""Flags without = should be preserved without dedup issues."""
|
||||
args = _build_args(stealth_args=True, extra_args=["--disable-gpu", "--no-zygote"])
|
||||
assert "--disable-gpu" in args
|
||||
assert "--no-zygote" in args
|
||||
assert "--no-sandbox" in args
|
||||
|
||||
|
||||
def test_override_logs_debug(caplog):
|
||||
"""Should log debug message when an override happens."""
|
||||
import logging
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="cloakbrowser"):
|
||||
_build_args(stealth_args=True, extra_args=["--fingerprint=99887"])
|
||||
assert any("--fingerprint=" in r.message and "99887" in r.message for r in caplog.records)
|
||||
|
||||
Reference in New Issue
Block a user