From 10ddc5b683e85e08fa4b0d2ceb3abc06cce3f7f7 Mon Sep 17 00:00:00 2001 From: galuis116 Date: Sun, 14 Jun 2026 10:54:58 -0700 Subject: [PATCH] fix(api): allow maintainer sessions to reach validate-linked-issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session-path allowlist (canSessionAccessPath) listed check-before-start but omitted its sibling validate-linked-issue, so a non-operator maintainer using a browser/session token got 403 insufficient_role for their own repo before the handler ran — leaving the route's requireSessionRepoAccess guard dead. Add the path to the allowlist, mirroring check-before-start; the per-route guard still enforces repo scope. Closes #734 --- src/api/routes.ts | 5 +++++ test/unit/access-boundary.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/api/routes.ts b/src/api/routes.ts index 5c157ce4a7..7cb64baedc 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -3959,6 +3959,7 @@ function canSessionAccessPath(env: Env, identity: Extract { expect(await other.json()).toMatchObject({ error: "forbidden_repo" }); }); + it("a maintainer can REACH validate-linked-issue on their OWN repo, scoped per-repo (allowlist parity with check-before-start)", async () => { + const { app, env } = await setup(); + const { token } = await createSessionForGitHubUser(env, { login: "alice", id: 101 }); + const cookie = `gittensory_session=${token}`; + // Before the fix this returned 403 insufficient_role at the session allowlist (the route was omitted), + // even though the handler's requireSessionRepoAccess guard would admit a maintainer of their own repo. + const own = await app.request( + "/v1/repos/alice/repo-a/validate-linked-issue", + { method: "POST", headers: { cookie }, body: JSON.stringify({ issueNumber: 1 }) }, + env, + ); + expect(own.status).toBe(200); + // The per-route guard still scopes: maintainer of A cannot validate against B. + const other = await app.request( + "/v1/repos/bob/repo-b/validate-linked-issue", + { method: "POST", headers: { cookie }, body: JSON.stringify({ issueNumber: 1 }) }, + env, + ); + expect(other.status).toBe(403); + expect(await other.json()).toMatchObject({ error: "forbidden_repo" }); + }); + it("a pure miner (no maintainer role on any repo) cannot read ANY repo's maintainer settings", async () => { const { app, env } = await setup(); const { token } = await createSessionForGitHubUser(env, { login: "miner-only", id: 900 });