From e7074b54d73c07cc739957674a328f1e4de4f9e2 Mon Sep 17 00:00:00 2001 From: techcenter68 Date: Thu, 21 May 2026 09:55:54 -0700 Subject: [PATCH] fix(browse): GSTACK_CHROMIUM_NO_SANDBOX opt-out for Ubuntu/AppArmor (#1562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ubuntu/AppArmor configurations often block unprivileged Chromium sandboxing for headless agent sessions even for normal users — /qa hangs without --no-sandbox. The kernel policy denies the unprivileged user namespaces Chromium needs. Adds GSTACK_CHROMIUM_NO_SANDBOX=1 as an explicit user override that forces the sandbox off without changing the default for everyone else. Re-authored from PR #1562 onto v1.42.2.0's shouldEnableChromiumSandbox() helper — purely additive, preserves the headed-launch sandbox-on-by-default behavior that v1.42.2.0 shipped to kill the --no-sandbox yellow infobar. Three new regression tests cover: - linux + override=1 → false (the named use case) - darwin + override=1 → false (env wins on any platform) - override=0 → does NOT trigger (must be exactly "1") Original diff by @techcenter68 via #1562. Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/src/browser-manager.ts | 7 ++++++ browse/test/browser-manager-unit.test.ts | 28 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index 32f5ab769..b29d539c5 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -59,6 +59,13 @@ export function isCustomChromium(): boolean { */ export function shouldEnableChromiumSandbox(): boolean { if (process.platform === 'win32') return false; + // Explicit user override for Ubuntu/AppArmor and similar environments where + // unprivileged Chromium sandboxing is blocked even for normal users (the + // sandbox needs unprivileged user namespaces that the host policy denies, + // so /qa hangs without --no-sandbox). Setting GSTACK_CHROMIUM_NO_SANDBOX=1 + // forces the sandbox off without changing the default for everyone else. + // See #1562. + if (process.env.GSTACK_CHROMIUM_NO_SANDBOX === '1') return false; const isRoot = typeof process.getuid === 'function' && process.getuid() === 0; return !(process.env.CI || process.env.CONTAINER || isRoot); } diff --git a/browse/test/browser-manager-unit.test.ts b/browse/test/browser-manager-unit.test.ts index 37e94b41d..45bebc345 100644 --- a/browse/test/browser-manager-unit.test.ts +++ b/browse/test/browser-manager-unit.test.ts @@ -29,17 +29,20 @@ describe('shouldEnableChromiumSandbox', () => { const origPlatform = process.platform; const origCI = process.env.CI; const origContainer = process.env.CONTAINER; + const origNoSandbox = process.env.GSTACK_CHROMIUM_NO_SANDBOX; const origGetuid = process.getuid; beforeEach(() => { delete process.env.CI; delete process.env.CONTAINER; + delete process.env.GSTACK_CHROMIUM_NO_SANDBOX; }); afterEach(() => { Object.defineProperty(process, 'platform', { value: origPlatform }); if (origCI === undefined) delete process.env.CI; else process.env.CI = origCI; if (origContainer === undefined) delete process.env.CONTAINER; else process.env.CONTAINER = origContainer; + if (origNoSandbox === undefined) delete process.env.GSTACK_CHROMIUM_NO_SANDBOX; else process.env.GSTACK_CHROMIUM_NO_SANDBOX = origNoSandbox; process.getuid = origGetuid; }); @@ -90,6 +93,31 @@ describe('shouldEnableChromiumSandbox', () => { const { shouldEnableChromiumSandbox } = await import('../src/browser-manager'); expect(shouldEnableChromiumSandbox()).toBe(false); }); + + // #1562 — Ubuntu/AppArmor opt-in override + it('linux + GSTACK_CHROMIUM_NO_SANDBOX=1 → false (Ubuntu/AppArmor opt-out)', async () => { + setPlatform('linux'); + process.env.GSTACK_CHROMIUM_NO_SANDBOX = '1'; + process.getuid = (() => 1000) as typeof process.getuid; + const { shouldEnableChromiumSandbox } = await import('../src/browser-manager'); + expect(shouldEnableChromiumSandbox()).toBe(false); + }); + + it('darwin + GSTACK_CHROMIUM_NO_SANDBOX=1 → false (env override wins on any platform)', async () => { + setPlatform('darwin'); + process.env.GSTACK_CHROMIUM_NO_SANDBOX = '1'; + process.getuid = (() => 501) as typeof process.getuid; + const { shouldEnableChromiumSandbox } = await import('../src/browser-manager'); + expect(shouldEnableChromiumSandbox()).toBe(false); + }); + + it('GSTACK_CHROMIUM_NO_SANDBOX=0 → does NOT trigger override (must be exactly "1")', async () => { + setPlatform('linux'); + process.env.GSTACK_CHROMIUM_NO_SANDBOX = '0'; + process.getuid = (() => 1000) as typeof process.getuid; + const { shouldEnableChromiumSandbox } = await import('../src/browser-manager'); + expect(shouldEnableChromiumSandbox()).toBe(true); + }); }); // ─── resolveDisconnectCause ──────────────────────────────────────