From 1d03d5a2e96ed147ae5c55a2e42866179223c837 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 19 May 2026 17:23:57 -0700 Subject: [PATCH] feat: buildGStackLaunchArgs adds --gstack-suppress-prepare-stack-trace Pack 2 / B11 flag plumbing for the new error-preparestacktrace-stealth.patch in gbrowser/chromium/patches/. Always emit --gstack-suppress-prepare-stack-trace unless the caller explicitly sets GSTACK_CDP_STEALTH=off in the environment. Off by default in patch behavior (no-op without the C++ patch), so this is safe on stock Playwright Chromium too. Closes the Cloudflare canary trick where a page sets Error.prepareStackTrace and watches for it to fire during CDP serialization of a logged Error object. Tests: All 33 stealth/browser-manager tests pass. New cases: - GSTACK_CDP_STEALTH=off disables suppression - empty env still emits the always-on flag (count=1) - all-populated env now emits 7 flags (was 6) Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/src/stealth.ts | 10 ++++++++++ browse/test/stealth-layer-c.test.ts | 26 +++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/browse/src/stealth.ts b/browse/src/stealth.ts index f4e982f28..028431330 100644 --- a/browse/src/stealth.ts +++ b/browse/src/stealth.ts @@ -323,6 +323,16 @@ export function buildGStackLaunchArgs(): string[] { const memory = env.GSTACK_DEVICE_MEMORY; 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') { + args.push('--gstack-suppress-prepare-stack-trace'); + } + return args; } diff --git a/browse/test/stealth-layer-c.test.ts b/browse/test/stealth-layer-c.test.ts index 444a09313..afdf3d252 100644 --- a/browse/test/stealth-layer-c.test.ts +++ b/browse/test/stealth-layer-c.test.ts @@ -142,13 +142,18 @@ describe('buildGStackLaunchArgs — Pack 1 cmdline-switch construction', () => { } } - test('empty env produces empty arg list', () => { + test('empty env produces only the always-on prepare-stack-trace flag', () => { withEnv({}, () => { - expect(buildGStackLaunchArgs()).toEqual([]); + // 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', + ]); }); }); - test('all env values populated → all 6 switches emitted', () => { + test('all env values populated → all 7 switches emitted', () => { withEnv({ GSTACK_GPU_VENDOR: 'Apple Inc.', GSTACK_GPU_RENDERER: 'ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Max, Unspecified Version)', @@ -164,7 +169,8 @@ describe('buildGStackLaunchArgs — Pack 1 cmdline-switch construction', () => { expect(args).toContain('--gstack-ua-model=Apple M4 Max'); expect(args).toContain('--gstack-hw-concurrency=16'); expect(args).toContain('--gstack-device-memory=8'); - expect(args.length).toBe(6); + expect(args).toContain('--gstack-suppress-prepare-stack-trace'); + expect(args.length).toBe(7); }); }); @@ -189,7 +195,17 @@ 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(args).toEqual(['--gstack-hw-concurrency=12']); + // expect hw + the always-on prepare-stack-trace suppression + expect(args).toContain('--gstack-hw-concurrency=12'); + expect(args).toContain('--gstack-suppress-prepare-stack-trace'); + expect(args.length).toBe(2); + }); + }); + + test('GSTACK_CDP_STEALTH=off disables prepare-stack-trace suppression', () => { + withEnv({ GSTACK_CDP_STEALTH: 'off' }, () => { + const args = buildGStackLaunchArgs(); + expect(args).not.toContain('--gstack-suppress-prepare-stack-trace'); }); });