mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-06 16:17:56 +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
|
||||
const extensionPath = this.findExtensionPath();
|
||||
const { buildGStackLaunchArgs } = await import('./stealth');
|
||||
const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
|
||||
const launchArgs = [
|
||||
'--hide-crash-restore-bubble',
|
||||
// Anti-bot-detection: remove the navigator.webdriver flag that Playwright sets.
|
||||
// Sites like Google and NYTimes check this to block automation browsers.
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
// Anti-bot-detection: --disable-blink-features=AutomationControlled (and any
|
||||
// future blink-level tells) via the shared STEALTH_LAUNCH_ARGS constant — the
|
||||
// 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
|
||||
// C++ patches in gbrowser's Chromium build. Each switch is a no-op
|
||||
// on Chromium builds without the corresponding patch (the patch's
|
||||
@@ -1541,8 +1542,11 @@ export class BrowserManager {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const extensionPath = this.findExtensionPath();
|
||||
const { buildGStackLaunchArgs } = await import('./stealth');
|
||||
const launchArgs: string[] = ['--hide-crash-restore-bubble', ...buildGStackLaunchArgs()];
|
||||
const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
|
||||
// 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) {
|
||||
launchArgs.push(`--disable-extensions-except=${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);
|
||||
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