diff --git a/.gstack/pregate.json b/.gstack/pregate.json index 8b61aafb9..737703bbe 100644 --- a/.gstack/pregate.json +++ b/.gstack/pregate.json @@ -4,6 +4,7 @@ "remote_gate": "bash scripts/ubicloud/pregate-remote.sh", "dependencies": { ".github/workflows/**": ["test/pregate-selection.test.ts", "test/free-tests-workflow-wiring.test.ts"], + "patches/**": ["browse/test/playwright-core-patch.test.ts"], "scripts/gen-ship-goldens.ts": ["test/host-config.test.ts"], "bun.lock": [], ".gitignore": [], diff --git a/BROWSER.md b/BROWSER.md index 69be0410d..5b773d782 100644 --- a/BROWSER.md +++ b/BROWSER.md @@ -1380,7 +1380,7 @@ browser's trust store and updates. eight eMudhra intermediate CAs by CRLSet on 2026-10-17, the day their key compromise was reported, and scheduled the removal of their root, emSign ECC Root CA - G3, from the Chrome Root Store for 2026-10-27. The root store built -into gstack's pinned Playwright 1.62.1 (Chromium 151) still trusts that root, so +into gstack's pinned Playwright 1.64.1 (Chromium 156) still trusts that root, so the headless browser accepts certificates those intermediates issued. That lasts until gstack moves to the first Playwright release whose Chromium is 158.0.8115.0 or later, the first Chrome for Testing build measured to refuse diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d3f5c18..3291dc83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.94.31.0] - 2026-11-03 + +**gstack's browser now runs Chromium 156, which fixes CVE-2026-106378, a macOS sandbox escape ([#3907](https://github.com/garrytan/gstack/issues/3907)).** Playwright 1.62.1 bundled Chromium 151, which predates the fix. Your next `./setup` or `/gstack-upgrade` downloads the new browser. + +### Fixed +- **Playwright 1.64.1** (Chromium 156.0.8078.4, revision 1248). Chromium's fix is commit `0c2f3e54b265` ("Disclaim TCC responsibility for GPU process", crbug.com/507596239). Its code is in the tagged source for 156.0.8078.4 and missing from 151.0.7922.34. We checked the source; nobody ran the exploit. A new test fails if the bundled Chromium ever drops below 155.0.8059.12, the first tag with the fix. +- **`browse handoff` opens the browser `GSTACK_CHROMIUM_PATH` names**, as the headless and headed launches already did. Before, handoff always fell back to Playwright's bundled Chromium. It never probes or self-heals a custom browser, and it doesn't load the sidebar extension into a build that already bakes it in. + +### For contributors +- `patches/playwright-core@1.64.1.patch` keeps the launch `windowsHide` hunk and the `TcpPortRandomizationWin` backport. Playwright 1.64 passes `windowsHide` on its `taskkill` call itself, so that hunk is gone; the installed-tree test still checks it. +- `.gstack/pregate.json` maps `patches/**` to `browse/test/playwright-core-patch.test.ts`, so the next Playwright bump gets a test lane without renaming anything. +- Linux arm64 now downloads the Chrome for Testing build (`chrome-linux-arm64.zip`). + ## [1.94.30.0] - 2026-11-03 **/ship sends more diffs to full review: spend-binding code, eval scorers, a repo's own risk paths and docs that print secrets now count as risk, and a lane can declare a class the matchers miss (GSTA-258 build C).** Today's reviewer panel stays the default. The one-reader shape for non-risk code ships as an opt-in (`review.panel risk`) because its no-loss replay didn't pass: with only the diff and no repository, the reader found 2 of 68 non-risk blockers, so the replay is being redone with readers working in a checkout. A repo with platform review on can skip /ship's readers on plain docs. diff --git a/VERSION b/VERSION index c7cfdb7de..7b2979a0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.94.30.0 +1.94.31.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 01ffae367..600bfdbfc 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.94.30.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.94.31.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index 058590a71..d672b0ff7 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -2215,7 +2215,11 @@ export class BrowserManager { // STEALTH_LAUNCH_ARGS the handed-off browser kept the AutomationControlled // tell that the other two paths strip. const launchArgs: string[] = ['--hide-crash-restore-bubble', ...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs(), ...this.proxyLaunchArgs()]; - if (extensionPath) { + if (extensionPath && isCustomChromium()) { + // Same rule as launchHeaded(): a build that bakes the extension in + // crashes when it is loaded twice. + console.log('[browse] Handoff: custom Chromium bakes in the extension — skipping --load-extension'); + } else if (extensionPath) { launchArgs.push(`--disable-extensions-except=${extensionPath}`); launchArgs.push(`--load-extension=${extensionPath}`); // Auth token is served via POST /extension-token (pinned-origin @@ -2234,27 +2238,31 @@ export class BrowserManager { fs.mkdirSync(userDataDir, { recursive: true }); cleanSingletonLocks(userDataDir); - // Self-heal probe (#2242): handoff always launches the Playwright-cache - // bundle (this launchPersistentContext call passes no executablePath), - // so a bundle poisoned by the old in-place rebrand would GPU-crash here - // exactly like launchHeaded(). Same probe, same contract: a - // GSTACK_CHROMIUM_PATH bundle is never passed in. The rethrown typed - // error surfaces through the outer catch as the actionable - // "Cannot open headed browser" message, headless browser untouched. - try { - probePoisonedChromiumBundle(chromium.executablePath()); - } catch (err: unknown) { - if (err instanceof PoisonedBundleError) throw err; - // Probe failures (no bundle yet, EACCES) fall through to launch. + // #3907: same custom-binary support as launch()/launchHeaded(). + const executablePath = process.env.GSTACK_CHROMIUM_PATH || undefined; + + // Self-heal probe (#2242): without a custom path, handoff launches the + // Playwright-cache bundle, so a bundle poisoned by the old in-place + // rebrand would GPU-crash here exactly like launchHeaded(). Same probe, + // same contract: a GSTACK_CHROMIUM_PATH bundle is never probed. The + // rethrown typed error surfaces through the outer catch as the + // actionable "Cannot open headed browser" message, headless browser + // untouched. + if (!executablePath) { + try { + probePoisonedChromiumBundle(chromium.executablePath()); + } catch (err: unknown) { + if (err instanceof PoisonedBundleError) throw err; + // Probe failures (no bundle yet, EACCES) fall through to launch. + } } // T1: same automation-tell-stripping defaults as launchHeaded(). // The handoff path (headless → headed re-launch) takes the same // anti-detection posture. const { STEALTH_IGNORE_DEFAULT_ARGS } = await import('./stealth'); - // XProtect self-heal wrapper (P0 #2554): handoff always launches the - // Playwright-cache bundle (no executablePath), so the heal applies - // exactly as in launch()/launchHeaded(). + // XProtect self-heal wrapper (P0 #2554), scoped exactly as in + // launch()/launchHeaded(): never heals a GSTACK_CHROMIUM_PATH bundle. newContext = await launchWithXProtectHeal(() => chromium.launchPersistentContext(userDataDir, { headless: false, ...(this.xvfb ? { env: { ...process.env, DISPLAY: this.xvfb.display } } : {}), @@ -2268,10 +2276,11 @@ export class BrowserManager { chromiumSandbox: shouldEnableChromiumSandbox(), args: launchArgs, viewport: null, + ...(executablePath ? { executablePath } : {}), ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}), ignoreDefaultArgs: STEALTH_IGNORE_DEFAULT_ARGS, timeout: 15000, - })); + }), { usesCustomExecutable: Boolean(executablePath) }); if (this.closing) { await newContext.close().catch(() => {}); throw new Error('Browser is shutting down'); diff --git a/browse/test/browser-manager-custom-chromium.test.ts b/browse/test/browser-manager-custom-chromium.test.ts index 782a8eece..4cd167320 100644 --- a/browse/test/browser-manager-custom-chromium.test.ts +++ b/browse/test/browser-manager-custom-chromium.test.ts @@ -1,5 +1,9 @@ -import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; -import { isCustomChromium } from '../src/browser-manager'; +import { describe, test, expect, beforeEach, afterEach, spyOn } from 'bun:test'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { chromium } from 'playwright'; +import { BrowserManager, isCustomChromium } from '../src/browser-manager'; describe('browser-manager: isCustomChromium', () => { let origPath: string | undefined; @@ -65,3 +69,72 @@ describe('browser-manager: isCustomChromium', () => { expect(isCustomChromium()).toBe(false); }); }); + + +// #3907: handoff() must launch the browser GSTACK_CHROMIUM_PATH names, like +// launch() and launchHeaded(). The Playwright launch is a spy that rejects, so +// no window opens and handoff() returns its "Cannot open headed browser" error. +describe('handoff: GSTACK_CHROMIUM_PATH', () => { + const EXTENSION = '/opt/gstack/extension'; + const ENV_KEYS = ['GSTACK_CHROMIUM_PATH', 'GSTACK_CHROMIUM_KIND', 'CHROMIUM_PROFILE'] as const; + const saved: Record = {}; + let tmpDir: string; + let launch: ReturnType; + let bundledPath: ReturnType; + + function promotable(): BrowserManager { + const manager = new BrowserManager(); + Object.assign(manager as unknown as Record, { + browser: {}, + context: {}, + saveState: async () => ({ cookies: [], pages: [] }), + ensureHeadedDisplay: async () => {}, + findExtensionPath: () => EXTENSION, + }); + return manager; + } + + const launchOptions = () => (launch.mock.calls[0] as [string, { executablePath?: string; args: string[] }])[1]; + + beforeEach(() => { + for (const key of ENV_KEYS) saved[key] = process.env[key]; + delete process.env.GSTACK_CHROMIUM_PATH; + delete process.env.GSTACK_CHROMIUM_KIND; + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'handoff-custom-chromium-')); + process.env.CHROMIUM_PROFILE = path.join(tmpDir, 'profile'); + launch = spyOn(chromium, 'launchPersistentContext').mockRejectedValue(new Error('launch spy')); + bundledPath = spyOn(chromium, 'executablePath').mockReturnValue(path.join(tmpDir, 'no-bundle', 'chrome')); + }); + + afterEach(() => { + launch.mockRestore(); + bundledPath.mockRestore(); + for (const key of ENV_KEYS) { + if (saved[key] === undefined) delete process.env[key]; + else process.env[key] = saved[key]; + } + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + test('launches the GSTACK_CHROMIUM_PATH binary and never probes the Playwright bundle', async () => { + process.env.GSTACK_CHROMIUM_PATH = '/opt/chrome/chrome'; + expect(await promotable().handoff('custom')).toContain('Cannot open headed browser'); + expect(launch).toHaveBeenCalledTimes(1); + expect(launchOptions().executablePath).toBe('/opt/chrome/chrome'); + expect(bundledPath).not.toHaveBeenCalled(); + }); + + test('without GSTACK_CHROMIUM_PATH it launches the probed Playwright bundle', async () => { + expect(await promotable().handoff('bundled')).toContain('Cannot open headed browser'); + expect('executablePath' in launchOptions()).toBe(false); + expect(bundledPath).toHaveBeenCalled(); + expect(launchOptions().args).toContain(`--load-extension=${EXTENSION}`); + }); + + test('a build that bakes the extension in never gets it loaded twice', async () => { + process.env.GSTACK_CHROMIUM_PATH = '/Applications/GBrowser.app/Contents/MacOS/GBrowser'; + await promotable().handoff('baked'); + expect(launchOptions().executablePath).toBe('/Applications/GBrowser.app/Contents/MacOS/GBrowser'); + expect(launchOptions().args.filter((arg) => arg.includes('extension'))).toEqual([]); + }); +}); diff --git a/browse/test/cookie-import-native-qualification.ts b/browse/test/cookie-import-native-qualification.ts index 8c703c9fe..5ba173c60 100644 --- a/browse/test/cookie-import-native-qualification.ts +++ b/browse/test/cookie-import-native-qualification.ts @@ -66,7 +66,7 @@ const nodeInfo = JSON.parse(nodeProbe.stdout); if (nodeInfo.architecture !== process.arch || !['x64', 'arm64'].includes(process.arch)) incomplete('runtime_architecture_mismatch'); const require = createRequire(import.meta.url); const playwrightVersion = require('playwright/package.json').version; -if (playwrightVersion !== '1.62.1') incomplete('playwright_1_62_1_required'); +if (playwrightVersion !== '1.64.1') incomplete('playwright_1_64_1_required'); const versionProbe = spawnSync(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), [ '-NoProfile', '-NonInteractive', '-Command', NATIVE_BROWSER_VERSION_COMMAND, ], { env: { ...environment, GSTACK_QUALIFY_BROWSER_EXE: executable }, encoding: 'utf8', timeout: 10_000, windowsHide: true }); diff --git a/browse/test/cookie-import-native.test.ts b/browse/test/cookie-import-native.test.ts index 11c462355..013d00da1 100644 --- a/browse/test/cookie-import-native.test.ts +++ b/browse/test/cookie-import-native.test.ts @@ -213,7 +213,7 @@ async function qualifiedAdapterFixture() { const receipt = { browserName: 'Edge', architecture: runtime.architecture, windowsRelease: release(), executableSha256: '0'.repeat(64), nodeVersion: runtime.version, bunVersion: Bun.version, - playwrightVersion: '1.62.1', sourceHashes, + playwrightVersion: '1.64.1', sourceHashes, }; const virtualRoot = `C:\\gstack-${path.basename(fixture)}`; const windowsEnv = { LOCALAPPDATA: `${virtualRoot}\\Local`, PROGRAMFILES: `${virtualRoot}\\Apps`, 'PROGRAMFILES(X86)': `${virtualRoot}\\Apps32` }; diff --git a/browse/test/playwright-core-patch.test.ts b/browse/test/playwright-core-patch.test.ts index f0974cbd3..eb34ed1d2 100644 --- a/browse/test/playwright-core-patch.test.ts +++ b/browse/test/playwright-core-patch.test.ts @@ -1,8 +1,10 @@ /** - * #2160/#1989: playwright-core is bun-patched to pass windowsHide at its two + * #2160/#1989: playwright-core must pass windowsHide at its two * Windows-visible child_process sites — the browser launch spawn (Node * defaults windowsHide to FALSE for spawn, so browser children could flash a - * console window) and the force-kill taskkill spawnSync. This is the repo's + * console window) and the force-kill taskkill spawnSync. The launch site is + * bun-patched; the taskkill site is upstream since Playwright 1.64 (#3907), + * so only the installed-tree check below covers it. This is the repo's * first patchedDependencies entry; these static checks pin the three-legged * coherence (patch file ↔ package.json ↔ installed tree) so a playwright * bump that forgets to re-target the patch fails CI instead of silently @@ -35,15 +37,14 @@ describe('playwright-core windowsHide patch (#2160, #1989)', () => { expect(pkg.patchedDependencies[key]).toBe(`patches/playwright-core@${version}.patch`); }); - test('the patch file exists and carries both windowsHide sites', () => { + test('the patch file exists and carries the launch windowsHide site', () => { const version = installedPlaywrightCoreVersion(); const patchPath = path.join(ROOT, 'patches', `playwright-core@${version}.patch`); expect(fs.existsSync(patchPath)).toBe(true); + expect(fs.existsSync(path.join(ROOT, 'patches', 'playwright-core@1.62.1.patch'))).toBe(false); const patch = fs.readFileSync(patchPath, 'utf-8'); // Launch spawnOptions site. expect(patch).toContain('+ windowsHide: true,'); - // taskkill force-kill site. - expect(patch).toContain('shell: true, windowsHide: true'); }); test('bun.lock is coherent: the lockfile records the patched dependency', () => { @@ -63,13 +64,28 @@ describe('playwright-core windowsHide patch (#2160, #1989)', () => { // Windows Free Tests 40656299153 and 41056150287 (cookie-picker-ui, serial retry passed): goto failed with // net::ERR_NO_BUFFER_SPACE on 127.0.0.1. Chromium's TcpPortRandomizationWin raises WSAENOBUFS with ports free; -// microsoft/playwright#43605 disables it in 1.65, and the 1.62.1 patch backports it; drop this block at 1.65. +// microsoft/playwright#43605 disables it in 1.65, and the 1.64.1 patch backports it; drop this block at 1.65. describe('playwright-core TcpPortRandomizationWin backport (microsoft/playwright#43605)', () => { test('the patch and the installed tree disable the feature in the --disable-features list', () => { - expect(fs.readFileSync(path.join(ROOT, 'patches', 'playwright-core@1.62.1.patch'), 'utf-8')).toContain('+ "TcpPortRandomizationWin"'); + expect(fs.readFileSync(path.join(ROOT, 'patches', 'playwright-core@1.64.1.patch'), 'utf-8')).toContain('+ "TcpPortRandomizationWin"'); const bundle = fs.readFileSync(path.join(ROOT, 'node_modules', 'playwright-core', 'lib', 'coreBundle.js'), 'utf-8'); const list = bundle.slice(bundle.indexOf('disabledFeatures = ['), bundle.indexOf('].filter(Boolean);', bundle.indexOf('disabledFeatures = ['))); expect(list).toContain('"TcpPortRandomizationWin"'); expect(bundle).toContain('"--disable-features=" + disabledFeatures.join(",")'); }); }); + +// #3907: CVE-2026-106378 (macOS GPU-process sandbox escape, crbug.com/507596239) is fixed by Chromium +// commit 0c2f3e54b265 (main@{#1695786}), first tagged in 155.0.8059.12. Playwright 1.62.1 shipped +// 151.0.7922.34, which lacks it. A Playwright pin whose bundled Chromium is older reopens the escape. +describe('bundled Chromium carries the CVE-2026-106378 fix (#3907)', () => { + test('playwright-core pins Chromium 155.0.8059.12 or later', () => { + const browsers = JSON.parse(fs.readFileSync( + path.join(ROOT, 'node_modules', 'playwright-core', 'browsers.json'), 'utf-8', + )) as { browsers: Array<{ name: string; browserVersion: string }> }; + const version = browsers.browsers.find((b) => b.name === 'chromium')!.browserVersion.split('.').map(Number); + const floor = [155, 0, 8059, 12]; + const cmp = version.map((part, i) => part - floor[i]).find((d) => d !== 0) ?? 0; + expect(cmp).toBeGreaterThanOrEqual(0); + }); +}); diff --git a/browse/test/security-audit-r2.test.ts b/browse/test/security-audit-r2.test.ts index cd123f5db..9db45e9a1 100644 --- a/browse/test/security-audit-r2.test.ts +++ b/browse/test/security-audit-r2.test.ts @@ -251,7 +251,7 @@ describe('cookie-import domain validation', () => { reset: 'not_requested', verification: { verified: false, reason: 'not_requested' }, message: 'Cookie copy complete.', }); let currentUrl = 'https://example.test'; - const page = { url: () => currentUrl, isClosed: () => false }; + const page = { url: () => currentUrl, isClosed: () => false } as any; const session = { getPage: () => page, getActiveFrameOrPage: () => page, getFrame: () => null } as any; const manager = { trackCookieImportDomains() {} } as any; try { diff --git a/bun.lock b/bun.lock index a32b356bc..d3d3ffbb5 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,7 @@ "html-to-docx": "1.8.0", "html-to-vdom": "0.7.0", "marked": "^18.1.1", - "playwright": "^1.62.1", + "playwright": "^1.64.1", "sharp": "^0.35.4", "socks": "^2.8.10", "xterm": "^5.3.0", @@ -28,7 +28,7 @@ }, }, "patchedDependencies": { - "playwright-core@1.62.1": "patches/playwright-core@1.62.1.patch", + "playwright-core@1.64.1": "patches/playwright-core@1.64.1.patch", }, "overrides": { "@hono/node-server": "2.1.6", @@ -335,8 +335,6 @@ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -469,9 +467,9 @@ "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="], - "playwright": ["playwright@1.62.1", "", { "dependencies": { "playwright-core": "1.62.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg=="], + "playwright": ["playwright@1.64.1", "", { "dependencies": { "playwright-core": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA=="], - "playwright-core": ["playwright-core@1.62.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw=="], + "playwright-core": ["playwright-core@1.64.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ=="], "prettier": ["prettier@3.9.10", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA=="], diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 796299069..fe878b5cb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2091,8 +2091,8 @@ browse chain 'goto https://example.com | text' ### `Chromium at GSTACK_CHROMIUM_PATH= failed to launch: ...` -**Meaning.** Headless browse and make-pdf now use `GSTACK_CHROMIUM_PATH` -(NixOS, macOS 13). That binary could not start. +**Meaning.** Headless browse, headed browse, `browse handoff` and make-pdf use +`GSTACK_CHROMIUM_PATH` (NixOS, macOS 13). That binary could not start. **Fix.** Point `GSTACK_CHROMIUM_PATH` at a working Chromium, or `unset GSTACK_CHROMIUM_PATH` to use gstack's bundled one. diff --git a/package.json b/package.json index 2688f1f42..d19cceffc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.94.30", + "version": "1.94.31", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", @@ -77,7 +77,7 @@ "html-to-docx": "1.8.0", "html-to-vdom": "0.7.0", "marked": "^18.1.1", - "playwright": "^1.62.1", + "playwright": "^1.64.1", "sharp": "^0.35.4", "socks": "^2.8.10", "xterm": "^5.3.0", @@ -115,6 +115,6 @@ "sharp": "0.35.5" }, "patchedDependencies": { - "playwright-core@1.62.1": "patches/playwright-core@1.62.1.patch" + "playwright-core@1.64.1": "patches/playwright-core@1.64.1.patch" } } diff --git a/patches/playwright-core@1.62.1.patch b/patches/playwright-core@1.64.1.patch similarity index 56% rename from patches/playwright-core@1.62.1.patch rename to patches/playwright-core@1.64.1.patch index e301f1bf0..41b76c9e6 100644 --- a/patches/playwright-core@1.62.1.patch +++ b/patches/playwright-core@1.64.1.patch @@ -1,8 +1,8 @@ diff --git a/lib/coreBundle.js b/lib/coreBundle.js -index d9041dd0f009072511a7d808dec83121ff29e127..542acd31dd7e8ae084de952dc42ec63d0caba849 100644 +index 6893f1cf133efec2c5dccc35c77cc7a53870ccbc..7990e202c5b574ca5ea4c3e73c15c2da43a5b980 100644 --- a/lib/coreBundle.js +++ b/lib/coreBundle.js -@@ -8906,6 +8906,10 @@ async function launchProcess(options) { +@@ -10031,6 +10031,10 @@ async function launchProcess(options) { env: options.env, cwd: options.cwd, shell: options.shell, @@ -13,16 +13,7 @@ index d9041dd0f009072511a7d808dec83121ff29e127..542acd31dd7e8ae084de952dc42ec63d stdio }; const spawnedProcess = childProcess.spawn(options.command, options.args || [], spawnOptions); -@@ -8985,7 +8989,7 @@ async function launchProcess(options) { - options.log(`[pid=${spawnedProcess.pid}] `); - try { - if (process.platform === "win32") { -- const taskkillProcess = childProcess.spawnSync(`taskkill /pid ${spawnedProcess.pid} /T /F`, { shell: true }); -+ const taskkillProcess = childProcess.spawnSync(`taskkill /pid ${spawnedProcess.pid} /T /F`, { shell: true, windowsHide: true }); - const [stdout2, stderr2] = [taskkillProcess.stdout.toString(), taskkillProcess.stderr.toString()]; - if (stdout2) - options.log(`[pid=${spawnedProcess.pid}] taskkill stdout: ${stdout2}`); -@@ -34634,7 +34638,10 @@ var init_chromiumSwitches = __esm({ +@@ -36204,7 +36208,10 @@ var init_chromiumSwitches = __esm({ // Disables forced sign-in in Edge. "msForceBrowserSignIn", // Disables updating the preferred version in LaunchServices preferences on mac. diff --git a/scripts/typecheck-test-baseline.json b/scripts/typecheck-test-baseline.json index 4d1c9dc9b..16f3bdea4 100644 --- a/scripts/typecheck-test-baseline.json +++ b/scripts/typecheck-test-baseline.json @@ -41,7 +41,6 @@ "browse/test/handoff.test.ts\tTS2554\tExpected 4 arguments, but got 3.": 2, "browse/test/pair-agent-e2e.test.ts\tTS2532\tObject is possibly 'undefined'.": 1, "browse/test/pty-inject-scan.test.ts\tTS2345\tArgument of type '{ Authorization?: undefined; } | { Authorization: string; }' is not assignable to parameter of type 'Record'. Type '{ Authorization?: undefined; }' is not assignable to type 'Record'. Property 'Authorization' is incompatible with index signature. Type 'undefined' is not assignable to type 'string'.": 1, - "browse/test/security-audit-r2.test.ts\tTS2769\tNo overload matches this call. The last overload gave the following error. Type '{ isClosed: () => boolean; url: () => string; }' is missing the following properties from type 'Page': evaluate, evaluateHandle, addInitScript, $, and 110 more.": 1, "browse/test/server-auth.test.ts\tTS2345\tArgument of type '{ Host: string; Origin: string; } | { Host?: undefined; Origin: string; } | { Authorization: string; Host: string; Origin?: undefined; }' is not assignable to parameter of type 'Record'. Type '{ Host?: undefined; Origin: string; }' is not assignable to type 'Record'. Property 'Host' is incompatible with index signature. Type 'undefined' is not assignable to type 'string'.": 1, "browse/test/server-factory.test.ts\tTS2769\tNo overload matches this call. The last overload gave the following error. Argument of type '\"local\"' is not assignable to parameter of type 'undefined'.": 1, "browse/test/server-proxy-fail-fast.test.ts\tTS2339\tProperty 'subarray' does not exist on type 'string | NonSharedBuffer'. Property 'subarray' does not exist on type 'string'.": 1, diff --git a/test/setup-playwright-platform.test.ts b/test/setup-playwright-platform.test.ts index d366a906a..5a736adf3 100644 --- a/test/setup-playwright-platform.test.ts +++ b/test/setup-playwright-platform.test.ts @@ -48,8 +48,8 @@ describe('Ubuntu fallback retains CPU architecture', () => { cwd: ROOT, env: { PATH: process.env.PATH, HOME: temp(), PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: override }, encoding: 'utf8', timeout: 20_000, }); expect(dryRun.status, dryRun.stderr).toBe(0); - expect(dryRun.stdout).toContain(expected === 'arm64' ? 'chromium-linux-arm64.zip' : 'chrome-linux64.zip'); - expect(dryRun.stdout).not.toContain(expected === 'arm64' ? 'chrome-linux64.zip' : 'chromium-linux-arm64.zip'); + expect(dryRun.stdout).toContain(expected === 'arm64' ? 'chrome-linux-arm64.zip' : 'chrome-linux64.zip'); + expect(dryRun.stdout).not.toContain(expected === 'arm64' ? 'chrome-linux64.zip' : 'chrome-linux-arm64.zip'); }, 30_000); } for (const cpu of ['riscv64', 'i686', 'unknown']) test(`${cpu} reports unsupported instead of installing x64`, () => {