mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-10 08:10:37 +02:00
v1.40.0.1 fix(browse): enable Chromium sandbox on headed launchPersistentContext
Playwright auto-adds --no-sandbox whenever chromiumSandbox !== true (playwright-core/lib/server/chromium/chromium.js:291-292). The headless chromium.launch() site set the option; the two headed sites (launchHeaded() and handoff()) did not. Every headed launch on macOS and Linux showed Chromium's yellow "unsupported command-line flag: --no-sandbox" infobar. Introduces shouldEnableChromiumSandbox() — centralizes the Win32 / CI / CONTAINER / root heuristic that previously lived only in the headless path's explicit --no-sandbox push at :225. All three launch sites now use the helper, and six unit tests pin the policy across darwin, linux, win32, CI, CONTAINER, and root. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
026751ea20
commit
a835de39b0
@@ -1,5 +1,39 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.40.0.1] - 2026-05-19
|
||||||
|
|
||||||
|
## **Headed Chromium launches stop shipping `--no-sandbox` to the browser on macOS and Linux.**
|
||||||
|
## **Yellow "unsupported command-line flag" infobar disappears for every dev who runs browse headed.**
|
||||||
|
|
||||||
|
`launchPersistentContext()` was missing the `chromiumSandbox` option, so Playwright's chromium launcher auto-added `--no-sandbox` on every headed launch (logic at `playwright-core/lib/server/chromium/chromium.js:291-292`: when `chromiumSandbox !== true`, push `--no-sandbox`). The headless `chromium.launch()` site set the option correctly; the two headed sites (`launchHeaded()` and `handoff()`) did not. Result: anyone running browse headed saw Chromium's bad-flags yellow infobar across the top of every tab. v1.40.0.1 introduces a shared `shouldEnableChromiumSandbox()` policy used by all three launch sites and pins it with unit tests so this can't regress silently.
|
||||||
|
|
||||||
|
### The numbers that matter
|
||||||
|
|
||||||
|
Source: `bun test browse/test/browser-manager-unit.test.ts` ... 8 tests, all green.
|
||||||
|
|
||||||
|
| Surface | Before | After |
|
||||||
|
|---|---|---|
|
||||||
|
| macOS dev, `bun run dev` headed (or `launchHeaded`) | Yellow `--no-sandbox` infobar on every launch | No infobar |
|
||||||
|
| Linux non-root, non-CI dev, headed launch | Yellow `--no-sandbox` infobar on every launch | No infobar |
|
||||||
|
| Linux root / Docker / CI headed launch | `--no-sandbox` reaches Chromium (correct), no infobar (acceptable since usually headless) | Same; sandbox correctly off |
|
||||||
|
| Windows headed launch | Sandbox off (GitHub #276 Bun→Node chain) | Same; explicitly preserved by helper |
|
||||||
|
| `handoff()` (headless→headed re-launch) | Yellow infobar same as `launchHeaded` | No infobar |
|
||||||
|
|
||||||
|
### What this means for builders
|
||||||
|
|
||||||
|
If you run `browse` headed on macOS or Linux dev, the yellow "unsupported command-line flag: --no-sandbox" warning is gone. Container, root, and CI environments still get sandbox off (correct, the kernel can't engage it there). The fix is one helper plus three single-line additions, pinned by `shouldEnableChromiumSandbox` unit tests so a future refactor can't silently regress the behavior. Pull and your next headed launch is clean.
|
||||||
|
|
||||||
|
### Itemized changes
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
|
||||||
|
- `browse/src/browser-manager.ts` ... `launchPersistentContext()` calls in `launchHeaded()` and `handoff()` now pass `chromiumSandbox`, so Playwright stops auto-adding `--no-sandbox` on every headed launch. Headless `launch()` switches to the same helper for consistency.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
|
||||||
|
- `browse/src/browser-manager.ts` (new export) ... `shouldEnableChromiumSandbox()` centralizes the Win32 / CI / CONTAINER / root heuristic that previously lived only in the headless path's explicit `--no-sandbox` push.
|
||||||
|
- `browse/test/browser-manager-unit.test.ts` ... six unit tests pinning `shouldEnableChromiumSandbox` across darwin, linux, win32, CI, CONTAINER, and root.
|
||||||
|
|
||||||
## [1.40.0.0] - 2026-05-16
|
## [1.40.0.0] - 2026-05-16
|
||||||
|
|
||||||
## **gbrain sync stops biting users across the install path, slug algorithm, federation queue, and `.env.local` footgun.**
|
## **gbrain sync stops biting users across the install path, slug algorithm, federation queue, and `.env.local` footgun.**
|
||||||
|
|||||||
@@ -40,6 +40,29 @@ export function isCustomChromium(): boolean {
|
|||||||
return p.includes('GBrowser') || p.includes('gbrowser');
|
return p.includes('GBrowser') || p.includes('gbrowser');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide whether Playwright should request Chromium's sandbox.
|
||||||
|
*
|
||||||
|
* Returns false on Windows (Bun→Node→Chromium chain breaks the sandbox,
|
||||||
|
* GitHub #276) and on Linux under root / CI / container (sandbox needs
|
||||||
|
* unprivileged user namespaces, which are missing for root and typically
|
||||||
|
* disabled in containers).
|
||||||
|
*
|
||||||
|
* When false, Playwright auto-adds --no-sandbox to the launch args — the
|
||||||
|
* desired behavior in those environments. When true, Playwright does NOT
|
||||||
|
* add --no-sandbox, which keeps Chromium's "unsupported command-line flag"
|
||||||
|
* yellow infobar from appearing on every headed launch.
|
||||||
|
*
|
||||||
|
* The headless launch path also pushes an explicit '--no-sandbox' into args
|
||||||
|
* when CI/CONTAINER/root is set; that push is now defensively redundant
|
||||||
|
* (Playwright will add it anyway when this returns false) and harmless.
|
||||||
|
*/
|
||||||
|
export function shouldEnableChromiumSandbox(): boolean {
|
||||||
|
if (process.platform === 'win32') return false;
|
||||||
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
||||||
|
return !(process.env.CI || process.env.CONTAINER || isRoot);
|
||||||
|
}
|
||||||
|
|
||||||
export type { RefEntry };
|
export type { RefEntry };
|
||||||
|
|
||||||
// Re-export TabSession for consumers
|
// Re-export TabSession for consumers
|
||||||
@@ -240,8 +263,10 @@ export class BrowserManager {
|
|||||||
headless: useHeadless,
|
headless: useHeadless,
|
||||||
// On Windows, Chromium's sandbox fails when the server is spawned through
|
// On Windows, Chromium's sandbox fails when the server is spawned through
|
||||||
// the Bun→Node process chain (GitHub #276). Disable it — local daemon
|
// the Bun→Node process chain (GitHub #276). Disable it — local daemon
|
||||||
// browsing user-specified URLs has marginal sandbox benefit.
|
// browsing user-specified URLs has marginal sandbox benefit. Also disabled
|
||||||
chromiumSandbox: process.platform !== 'win32',
|
// on Linux root/CI/container, where the sandbox requires unprivileged user
|
||||||
|
// namespaces that aren't available.
|
||||||
|
chromiumSandbox: shouldEnableChromiumSandbox(),
|
||||||
...(launchArgs.length > 0 ? { args: launchArgs } : {}),
|
...(launchArgs.length > 0 ? { args: launchArgs } : {}),
|
||||||
...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
|
...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
|
||||||
});
|
});
|
||||||
@@ -415,6 +440,10 @@ export class BrowserManager {
|
|||||||
|
|
||||||
this.context = await chromium.launchPersistentContext(userDataDir, {
|
this.context = await chromium.launchPersistentContext(userDataDir, {
|
||||||
headless: false,
|
headless: false,
|
||||||
|
// Match the sandbox policy used by launch() above. Without this,
|
||||||
|
// Playwright auto-adds --no-sandbox on every headed launch and the user
|
||||||
|
// sees Chromium's "unsupported command-line flag" yellow infobar.
|
||||||
|
chromiumSandbox: shouldEnableChromiumSandbox(),
|
||||||
args: launchArgs,
|
args: launchArgs,
|
||||||
viewport: null, // Use browser's default viewport (real window size)
|
viewport: null, // Use browser's default viewport (real window size)
|
||||||
userAgent: this.customUserAgent || customUA,
|
userAgent: this.customUserAgent || customUA,
|
||||||
@@ -1303,6 +1332,10 @@ export class BrowserManager {
|
|||||||
|
|
||||||
newContext = await chromium.launchPersistentContext(userDataDir, {
|
newContext = await chromium.launchPersistentContext(userDataDir, {
|
||||||
headless: false,
|
headless: false,
|
||||||
|
// Match the sandbox policy used by launchHeaded() / launch(). The
|
||||||
|
// handoff path is the headless→headed re-launch and shares the same
|
||||||
|
// anti-detection posture, including no spurious --no-sandbox infobar.
|
||||||
|
chromiumSandbox: shouldEnableChromiumSandbox(),
|
||||||
args: launchArgs,
|
args: launchArgs,
|
||||||
viewport: null,
|
viewport: null,
|
||||||
...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
|
...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect } from 'bun:test';
|
import { afterEach, beforeEach, describe, it, expect } from 'bun:test';
|
||||||
|
|
||||||
// ─── BrowserManager basic unit tests ─────────────────────────────
|
// ─── BrowserManager basic unit tests ─────────────────────────────
|
||||||
|
|
||||||
@@ -15,3 +15,78 @@ describe('BrowserManager defaults', () => {
|
|||||||
expect(bm.getRefMap()).toEqual([]);
|
expect(bm.getRefMap()).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── shouldEnableChromiumSandbox ─────────────────────────────────
|
||||||
|
//
|
||||||
|
// Pinning this is what prevents the "--no-sandbox" yellow infobar from
|
||||||
|
// regressing on headed launches. Playwright auto-adds --no-sandbox when
|
||||||
|
// chromiumSandbox !== true (playwright-core chromium.js:291-292), so all
|
||||||
|
// three launch sites in browser-manager.ts must pass the policy this
|
||||||
|
// helper computes.
|
||||||
|
|
||||||
|
describe('shouldEnableChromiumSandbox', () => {
|
||||||
|
const origPlatform = process.platform;
|
||||||
|
const origCI = process.env.CI;
|
||||||
|
const origContainer = process.env.CONTAINER;
|
||||||
|
const origGetuid = process.getuid;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.CI;
|
||||||
|
delete process.env.CONTAINER;
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
process.getuid = origGetuid;
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPlatform(p: NodeJS.Platform) {
|
||||||
|
Object.defineProperty(process, 'platform', { value: p });
|
||||||
|
}
|
||||||
|
|
||||||
|
it('darwin, no CI/CONTAINER/root → true', async () => {
|
||||||
|
setPlatform('darwin');
|
||||||
|
process.getuid = (() => 501) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('linux, no CI/CONTAINER/root → true', async () => {
|
||||||
|
setPlatform('linux');
|
||||||
|
process.getuid = (() => 1000) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('win32 → false (sandbox fails in Bun→Node→Chromium chain)', async () => {
|
||||||
|
setPlatform('win32');
|
||||||
|
process.getuid = (() => 1000) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('linux + CI=1 → false', async () => {
|
||||||
|
setPlatform('linux');
|
||||||
|
process.env.CI = '1';
|
||||||
|
process.getuid = (() => 1000) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('linux + CONTAINER=1 → false', async () => {
|
||||||
|
setPlatform('linux');
|
||||||
|
process.env.CONTAINER = '1';
|
||||||
|
process.getuid = (() => 1000) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('linux + root (uid 0) → false', async () => {
|
||||||
|
setPlatform('linux');
|
||||||
|
process.getuid = (() => 0) as typeof process.getuid;
|
||||||
|
const { shouldEnableChromiumSandbox } = await import('../src/browser-manager');
|
||||||
|
expect(shouldEnableChromiumSandbox()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user