From 3693cc2d8d6fed5bc040a31bd217d095ed1844b6 Mon Sep 17 00:00:00 2001 From: wsr3005 <49547899+wsr3005@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:23:56 +0800 Subject: [PATCH] api: preserve source auth for source-only routes --- src/api/server.ts | 2 +- test/auth-gate.test.ts | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/api/server.ts b/src/api/server.ts index 8b82a11f..f4b9ba47 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -257,7 +257,7 @@ async function gate( await deps.identity.refresh(); if (deps.identity.classify(actor.p).type !== "internal") actor = null; } - if (!isPublicRoute && requirePortalIdentity) { + if (!isPublicRoute && routeAuth !== "source" && requirePortalIdentity) { const webTurn = method === "POST" && pathname === "/v1/turns" && diff --git a/test/auth-gate.test.ts b/test/auth-gate.test.ts index 06def00d..91020bec 100644 --- a/test/auth-gate.test.ts +++ b/test/auth-gate.test.ts @@ -227,3 +227,23 @@ test("egress-audit ingest requires source auth: unsigned is 401, signed lands await srv.close(); } }); + +test("source-only run reads remain available under portal-identity enforcement", async () => { + const built = buildApp(testConfig({ dataDir: mkdtempSync(join(tmpdir(), "auth-run-read-")) })); + const server = createServer(built.app, { + signingSecret: SECRET, + runs: built.runs, + requireSignedPortalIdentity: true, + capabilitySecret: `${SECRET}-cap`, + portalIdentitySecret: `${SECRET}-portal`, + }); + server.listen(0); + const base = `http://localhost:${(server.address() as AddressInfo).port}`; + try { + const path = "/v1/runs/missing"; + const response = await fetch(`${base}${path}`, { headers: sign("GET", path, "") }); + assert.equal(response.status, 404); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } +});