From 988e6da6146935aff922e4af354ea4430626aabb Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:10:11 -0600 Subject: [PATCH] fix(auth): scope self-dogfood report access --- src/api/routes.ts | 7 +++ ...tes-self-dogfood-registration-pack.test.ts | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/api/routes.ts b/src/api/routes.ts index ff2a9c4508..311b2aa6c1 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -1530,6 +1530,13 @@ export function createApp() { app.get("/v1/app/self-dogfood/registration-pack", async (c) => { const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]); if (forbidden) return forbidden; + const identity = await authenticateRequestIdentity(c); + const fullName = resolveSelfDogfoodRepoFullName(c.env); + const repo = await getRepository(c.env, fullName); + if (identity?.kind === "session") { + const repoForbidden = await requireSessionRepoAccess(c, identity, fullName, repo); + if (repoForbidden) return repoForbidden; + } return c.json(await buildSelfDogfoodRegistrationPackResponse(c.env)); }); diff --git a/test/unit/routes-self-dogfood-registration-pack.test.ts b/test/unit/routes-self-dogfood-registration-pack.test.ts index 77316da193..096afdc51e 100644 --- a/test/unit/routes-self-dogfood-registration-pack.test.ts +++ b/test/unit/routes-self-dogfood-registration-pack.test.ts @@ -1,9 +1,11 @@ import { describe, expect, it } from "vitest"; import { createApp } from "../../src/api/routes"; import { createSessionForGitHubUser } from "../../src/auth/security"; +import { upsertInstallation, upsertRepositoryFromGitHub } from "../../src/db/repositories"; import { createTestEnv } from "../helpers/d1"; const SELF_DOGFOOD_PATH = "/v1/repos/JSONbored/gittensory/self-dogfood-registration-pack"; +const APP_SELF_DOGFOOD_PATH = "/v1/app/self-dogfood/registration-pack"; function apiHeaders(env: Env): Record { return { @@ -12,6 +14,23 @@ function apiHeaders(env: Env): Record { }; } +async function seedInstalledRepo(env: Env, installationId: number, owner: string, name: string): Promise { + await upsertInstallation(env, { + installation: { + id: installationId, + account: { login: owner, id: installationId, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", contents: "read" }, + events: ["repository"], + }, + }); + await upsertRepositoryFromGitHub( + env, + { name, full_name: `${owner}/${name}`, private: false, owner: { login: owner } }, + installationId, + ); +} + describe("self-dogfood registration-pack route auth", () => { it("rejects unauthenticated access to the repo-scoped route", async () => { const app = createApp(); @@ -43,6 +62,31 @@ describe("self-dogfood registration-pack route auth", () => { await expect(response.json()).resolves.toMatchObject({ error: "self_dogfood_repo_only", repoFullName: "JSONbored/gittensory" }); }); + it("rejects app-route sessions scoped only to an unrelated installed repo", async () => { + const app = createApp(); + const env = createTestEnv({ ADMIN_GITHUB_LOGINS: "" }); + await seedInstalledRepo(env, 201, "JSONbored", "gittensory"); + await seedInstalledRepo(env, 202, "unrelated-owner", "unrelated-repo"); + const { token } = await createSessionForGitHubUser(env, { login: "unrelated-owner", id: 202 }); + const response = await app.request(APP_SELF_DOGFOOD_PATH, { headers: { cookie: `gittensory_session=${token}` } }, env); + expect(response.status).toBe(403); + await expect(response.json()).resolves.toMatchObject({ error: "forbidden_repo" }); + }); + + it("allows app-route sessions scoped to the configured self-dogfood repo", async () => { + const app = createApp(); + const env = createTestEnv({ ADMIN_GITHUB_LOGINS: "" }); + await seedInstalledRepo(env, 201, "JSONbored", "gittensory"); + const { token } = await createSessionForGitHubUser(env, { login: "JSONbored", id: 201 }); + const response = await app.request(APP_SELF_DOGFOOD_PATH, { headers: { cookie: `gittensory_session=${token}` } }, env); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + kind: "gittensory_self_dogfood_registration_pack", + repoFullName: "JSONbored/gittensory", + privateOnly: true, + }); + }); + it("allows static-token access to the configured self-dogfood repo", async () => { const app = createApp(); const env = createTestEnv();