From be1a3ea778bae4d24bb83d7610efe19fbb8561c1 Mon Sep 17 00:00:00 2001 From: Xiangzhe Date: Fri, 21 Aug 2026 13:23:01 -0300 Subject: [PATCH] fix(security): add chatgpt-web-codex-doctor to SPAWN_CAPABLE_PATTERNS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manage-scope bypass veto's precise early-deny keys on SPAWN_CAPABLE_PATTERNS, but /api/providers/{id}/chatgpt-web-codex-doctor — a LOCAL_ONLY route that spawns a subprocess via getTunnelRuntimeStatus() — was in LOCAL_ONLY_API_PATTERNS without a matching spawn-capable pattern, so the two layers had drifted. Add the pattern plus a regression test asserting every regex-tier LOCAL_ONLY spawn route is covered, so the veto's exact early-deny stays in sync with the tier. Reported by @Zandereins via GHSA-9q3h-mjm5-f4gj (finding 1). --- src/shared/constants/spawnCapablePrefixes.ts | 1 + tests/unit/authz/routeGuard.test.ts | 22 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/shared/constants/spawnCapablePrefixes.ts b/src/shared/constants/spawnCapablePrefixes.ts index c23d6cc4a5..20787d7d2f 100644 --- a/src/shared/constants/spawnCapablePrefixes.ts +++ b/src/shared/constants/spawnCapablePrefixes.ts @@ -52,6 +52,7 @@ export const SPAWN_CAPABLE_PATTERNS: ReadonlyArray = [ /^\/api\/providers\/[^/]+\/login\/?$/, // pre-existing gap: in LOCAL_ONLY_API_PATTERNS today but never in a spawn-capable deny-list /^\/api\/providers\/[^/]+\/refresh-cursor\/?$/, // spawns cursor-agent via renewal.ts (Hard Rules #15 + #17) /^\/api\/providers\/cursor\/agent-availability\/?$/, // static path (no dynamic segment), but kept in this array alongside its /api/providers/ siblings rather than the flat SPAWN_CAPABLE_PREFIXES array — spawns cursor-agent status via checkCursorAgentAvailability()/getCachedCursorAgentAvailability() (Hard Rules #15 + #17) + /^\/api\/providers\/[^/]+\/chatgpt-web-codex-doctor\/?$/, // spawns via getTunnelRuntimeStatus() → spawnSync("...","runtimes status") (open-sse/executors/chatgpt-web-codex/tunnelClient.ts). Mirrors LOCAL_ONLY_API_PATTERNS in routeGuard.ts; keep the two in sync (GHSA-9q3h-mjm5-f4gj). ]; /** diff --git a/tests/unit/authz/routeGuard.test.ts b/tests/unit/authz/routeGuard.test.ts index 9d2e24672b..163f5bce41 100644 --- a/tests/unit/authz/routeGuard.test.ts +++ b/tests/unit/authz/routeGuard.test.ts @@ -6,6 +6,7 @@ import { isAlwaysProtectedPath, isLoopbackHost, } from "../../../src/server/authz/routeGuard.ts"; +import { SPAWN_CAPABLE_PATTERNS } from "../../../src/shared/constants/spawnCapablePrefixes.ts"; import { managementPolicy } from "../../../src/server/authz/policies/management.ts"; import { getMachineTokenSync } from "../../../src/lib/machineToken.ts"; import { CLI_TOKEN_HEADER } from "../../../src/server/authz/headers.ts"; @@ -50,6 +51,27 @@ test("isLocalOnlyBypassableByManageScope: non-local-only routes are not bypassab assert.equal(isLocalOnlyBypassableByManageScope("/api/settings"), false); }); +test("SPAWN_CAPABLE_PATTERNS covers every regex-tier LOCAL_ONLY spawn route (GHSA-9q3h-mjm5-f4gj)", () => { + // The manage-scope bypass veto's precise early-deny keys on + // SPAWN_CAPABLE_PATTERNS, so every LOCAL_ONLY_API_PATTERNS entry (a + // spawn-capable regex route) must have a matching pattern here — otherwise the + // two layers drift and a spawn route loses its exact early-deny. This guards + // against the chatgpt-web-codex-doctor drift and any future one. + const spawnRoutes = [ + "/api/providers/acct-1/login", + "/api/providers/acct-1/refresh-cursor", + "/api/providers/acct-1/chatgpt-web-codex-doctor", + ]; + for (const p of spawnRoutes) { + assert.equal(isLocalOnlyPath(p), true, `${p} must be LOCAL_ONLY`); + assert.equal( + SPAWN_CAPABLE_PATTERNS.some((re) => re.test(p)), + true, + `${p} is a LOCAL_ONLY spawn route but SPAWN_CAPABLE_PATTERNS does not cover it` + ); + } +}); + test("isAlwaysProtectedPath: /api/shutdown is always protected", () => { assert.equal(isAlwaysProtectedPath("/api/shutdown"), true); });