diff --git a/browse/src/stealth.ts b/browse/src/stealth.ts index 48b040061..01569d28f 100644 --- a/browse/src/stealth.ts +++ b/browse/src/stealth.ts @@ -515,12 +515,18 @@ export function buildGStackLaunchArgs(): string[] { if (memory) args.push(`--gstack-device-memory=${memory}`); // Pack 2 / B11: suppress user-defined Error.prepareStackTrace during - // V8 stack-trace formatting. Closes the Cloudflare Bot Management - // canary trick where a page sets prepareStackTrace and watches for - // it to fire during CDP serialization. Off by default — only set - // when the C++ patch is present (gbrowser builds), gstack hosts - // running stock Playwright Chromium leave it unset. - if (env.GSTACK_CDP_STEALTH !== 'off') { + // V8 stack-trace formatting. Closes the Cloudflare Bot Management canary + // trick where a page sets prepareStackTrace and watches for it to fire + // during CDP serialization. + // + // OPT-IN (off by default): only emitted when GSTACK_CDP_STEALTH is + // on/1/true. This switch is read by a C++ patch that only exists in + // gbrowser builds; gbd opts in by exporting GSTACK_CDP_STEALTH=on. Stock + // Playwright Chromium leaves it unset, so the flag never reaches a + // Chromium that wouldn't understand it. (Previously this was on-by-default + // unless GSTACK_CDP_STEALTH=off, which contradicted this very comment.) + const cdpStealth = env.GSTACK_CDP_STEALTH; + if (cdpStealth === 'on' || cdpStealth === '1' || cdpStealth === 'true') { args.push('--gstack-suppress-prepare-stack-trace'); } diff --git a/browse/test/stealth-layer-c.test.ts b/browse/test/stealth-layer-c.test.ts index afdf3d252..eeeef0b09 100644 --- a/browse/test/stealth-layer-c.test.ts +++ b/browse/test/stealth-layer-c.test.ts @@ -142,18 +142,16 @@ describe('buildGStackLaunchArgs — Pack 1 cmdline-switch construction', () => { } } - test('empty env produces only the always-on prepare-stack-trace flag', () => { + test('empty env produces no switches (suppress-stack is opt-in)', () => { withEnv({}, () => { - // The Pack 2 / B11 suppression flag is always emitted unless - // explicitly disabled via GSTACK_CDP_STEALTH=off. Six per-install - // flags fall through (nothing in env), so we expect just one. - expect(buildGStackLaunchArgs()).toEqual([ - '--gstack-suppress-prepare-stack-trace', - ]); + // All switches are opt-in: the six per-install flags fall through + // (nothing in env), and the Pack 2 / B11 suppression flag is only + // emitted when GSTACK_CDP_STEALTH is on/1/true. Empty env → []. + expect(buildGStackLaunchArgs()).toEqual([]); }); }); - test('all env values populated → all 7 switches emitted', () => { + test('all env values populated (incl. CDP stealth opt-in) → all 7 switches emitted', () => { withEnv({ GSTACK_GPU_VENDOR: 'Apple Inc.', GSTACK_GPU_RENDERER: 'ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Max, Unspecified Version)', @@ -161,6 +159,7 @@ describe('buildGStackLaunchArgs — Pack 1 cmdline-switch construction', () => { GSTACK_GPU_CHIPSET: 'Apple M4 Max', GSTACK_HW_CONCURRENCY: '16', GSTACK_DEVICE_MEMORY: '8', + GSTACK_CDP_STEALTH: 'on', }, () => { const args = buildGStackLaunchArgs(); expect(args).toContain('--gstack-gpu-vendor=Apple Inc.'); @@ -195,17 +194,26 @@ describe('buildGStackLaunchArgs — Pack 1 cmdline-switch construction', () => { test('partial env: only set switches that have values', () => { withEnv({ GSTACK_HW_CONCURRENCY: '12' }, () => { const args = buildGStackLaunchArgs(); - // expect hw + the always-on prepare-stack-trace suppression + // hw only — suppress-stack is opt-in and GSTACK_CDP_STEALTH is unset. expect(args).toContain('--gstack-hw-concurrency=12'); - expect(args).toContain('--gstack-suppress-prepare-stack-trace'); - expect(args.length).toBe(2); + expect(args).not.toContain('--gstack-suppress-prepare-stack-trace'); + expect(args.length).toBe(1); }); }); - test('GSTACK_CDP_STEALTH=off disables prepare-stack-trace suppression', () => { + test('prepare-stack-trace suppression is opt-in via GSTACK_CDP_STEALTH', () => { + // on/1/true enable it; off and unset omit it, so stock Playwright + // Chromium (no GSTACK_CDP_STEALTH) never receives the unknown switch. + for (const v of ['on', '1', 'true']) { + withEnv({ GSTACK_CDP_STEALTH: v }, () => { + expect(buildGStackLaunchArgs()).toContain('--gstack-suppress-prepare-stack-trace'); + }); + } withEnv({ GSTACK_CDP_STEALTH: 'off' }, () => { - const args = buildGStackLaunchArgs(); - expect(args).not.toContain('--gstack-suppress-prepare-stack-trace'); + expect(buildGStackLaunchArgs()).not.toContain('--gstack-suppress-prepare-stack-trace'); + }); + withEnv({}, () => { + expect(buildGStackLaunchArgs()).not.toContain('--gstack-suppress-prepare-stack-trace'); }); });