mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-09 17:38:40 +02:00
fix(browse): handoff() + launchHeaded() spread the shared STEALTH_LAUNCH_ARGS
handoff() built its launch args from only ['--hide-crash-restore-bubble', ...buildGStackLaunchArgs()], omitting STEALTH_LAUNCH_ARGS — so a handed-off browser kept the --disable-blink-features=AutomationControlled tell that launch() and launchHeaded() strip. launchHeaded() also hardcoded the flag as a literal. Both now spread the shared constant, so the AutomationControlled flag lives in one place across all three launch paths. Tripwires: STEALTH_LAUNCH_ARGS spread into >= 3 sites (no inline literal) and STEALTH_IGNORE_DEFAULT_ARGS wired into both persistent-context paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -437,12 +437,13 @@ export class BrowserManager {
|
|||||||
|
|
||||||
// Find the gstack extension directory for auto-loading
|
// Find the gstack extension directory for auto-loading
|
||||||
const extensionPath = this.findExtensionPath();
|
const extensionPath = this.findExtensionPath();
|
||||||
const { buildGStackLaunchArgs } = await import('./stealth');
|
const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
|
||||||
const launchArgs = [
|
const launchArgs = [
|
||||||
'--hide-crash-restore-bubble',
|
'--hide-crash-restore-bubble',
|
||||||
// Anti-bot-detection: remove the navigator.webdriver flag that Playwright sets.
|
// Anti-bot-detection: --disable-blink-features=AutomationControlled (and any
|
||||||
// Sites like Google and NYTimes check this to block automation browsers.
|
// future blink-level tells) via the shared STEALTH_LAUNCH_ARGS constant — the
|
||||||
'--disable-blink-features=AutomationControlled',
|
// same flag launch() and handoff() use, kept in one place instead of a literal.
|
||||||
|
...STEALTH_LAUNCH_ARGS,
|
||||||
// GStack Pack 1: per-install hardware/GPU/UA-CH overrides for the
|
// GStack Pack 1: per-install hardware/GPU/UA-CH overrides for the
|
||||||
// C++ patches in gbrowser's Chromium build. Each switch is a no-op
|
// C++ patches in gbrowser's Chromium build. Each switch is a no-op
|
||||||
// on Chromium builds without the corresponding patch (the patch's
|
// on Chromium builds without the corresponding patch (the patch's
|
||||||
@@ -1541,8 +1542,11 @@ export class BrowserManager {
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const extensionPath = this.findExtensionPath();
|
const extensionPath = this.findExtensionPath();
|
||||||
const { buildGStackLaunchArgs } = await import('./stealth');
|
const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
|
||||||
const launchArgs: string[] = ['--hide-crash-restore-bubble', ...buildGStackLaunchArgs()];
|
// Same blink-level stealth flags as launch()/launchHeaded(). Without
|
||||||
|
// STEALTH_LAUNCH_ARGS the handed-off browser kept the AutomationControlled
|
||||||
|
// tell that the other two paths strip.
|
||||||
|
const launchArgs: string[] = ['--hide-crash-restore-bubble', ...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs()];
|
||||||
if (extensionPath) {
|
if (extensionPath) {
|
||||||
launchArgs.push(`--disable-extensions-except=${extensionPath}`);
|
launchArgs.push(`--disable-extensions-except=${extensionPath}`);
|
||||||
launchArgs.push(`--load-extension=${extensionPath}`);
|
launchArgs.push(`--load-extension=${extensionPath}`);
|
||||||
|
|||||||
@@ -253,4 +253,40 @@ describe('stealth injected on every launch path', () => {
|
|||||||
const handoffBody = src.slice(handoffStart, resumeAnchor > 0 ? resumeAnchor : handoffStart + 4000);
|
const handoffBody = src.slice(handoffStart, resumeAnchor > 0 ? resumeAnchor : handoffStart + 4000);
|
||||||
expect(handoffBody).toContain('applyStealth(');
|
expect(handoffBody).toContain('applyStealth(');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('buildGStackLaunchArgs() is spread into all 3 launch sites', async () => {
|
||||||
|
// Same silent-drop regression class as applyStealth: a launch path that
|
||||||
|
// omits buildGStackLaunchArgs() loses the per-install GPU/UA/hardware
|
||||||
|
// cmdline spoof. launch(), launchHeaded(), and handoff() must all call it.
|
||||||
|
const { readFileSync } = await import('node:fs');
|
||||||
|
const { join } = await import('node:path');
|
||||||
|
const src = readFileSync(join(import.meta.dir, '..', 'src', 'browser-manager.ts'), 'utf-8');
|
||||||
|
const callSites = src.match(/buildGStackLaunchArgs\(\)/g) || [];
|
||||||
|
expect(callSites.length).toBeGreaterThanOrEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('STEALTH_LAUNCH_ARGS is spread into all 3 launch sites (no hardcoded literal)', async () => {
|
||||||
|
// The --disable-blink-features=AutomationControlled flag must come from the
|
||||||
|
// shared constant on launch(), launchHeaded(), AND handoff(). handoff()
|
||||||
|
// previously omitted it, leaving the AutomationControlled tell on the
|
||||||
|
// handed-off browser. No path may inline the literal instead.
|
||||||
|
const { readFileSync } = await import('node:fs');
|
||||||
|
const { join } = await import('node:path');
|
||||||
|
const src = readFileSync(join(import.meta.dir, '..', 'src', 'browser-manager.ts'), 'utf-8');
|
||||||
|
const spreads = src.match(/\.\.\.STEALTH_LAUNCH_ARGS/g) || [];
|
||||||
|
expect(spreads.length).toBeGreaterThanOrEqual(3);
|
||||||
|
// The literal must not be reintroduced in a launchArgs array (it belongs in
|
||||||
|
// the STEALTH_LAUNCH_ARGS constant in stealth.ts, not inline here).
|
||||||
|
expect(src).not.toContain("'--disable-blink-features=AutomationControlled'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('STEALTH_IGNORE_DEFAULT_ARGS is wired into both persistent-context paths', async () => {
|
||||||
|
// launchHeaded() and handoff() both launchPersistentContext and must strip
|
||||||
|
// Playwright's automation-tell defaults via the shared constant.
|
||||||
|
const { readFileSync } = await import('node:fs');
|
||||||
|
const { join } = await import('node:path');
|
||||||
|
const src = readFileSync(join(import.meta.dir, '..', 'src', 'browser-manager.ts'), 'utf-8');
|
||||||
|
const sites = src.match(/ignoreDefaultArgs:\s*STEALTH_IGNORE_DEFAULT_ARGS/g) || [];
|
||||||
|
expect(sites.length).toBeGreaterThanOrEqual(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user