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 ──────────────────────────────────────