fix(browse): make --gstack-suppress-prepare-stack-trace opt-in, not default-on

buildGStackLaunchArgs() pushed the flag unless GSTACK_CDP_STEALTH=off, i.e.
on-by-default — contradicting its own comment ("off by default, only for
gbrowser builds"). The switch is read by a C++ patch that only exists in
gbrowser; on stock Playwright Chromium it is an unknown switch.

Flip to opt-in: emit only when GSTACK_CDP_STEALTH is on/1/true. gbd opts in by
exporting GSTACK_CDP_STEALTH=on; stock installs leave it unset so the flag
never reaches a Chromium that wouldn't understand it. Comment now matches code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-18 00:58:45 -07:00
parent c389084a64
commit 248ca588fa
2 changed files with 34 additions and 20 deletions
+12 -6
View File
@@ -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');
}
+22 -14
View File
@@ -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');
});
});