From 751e00defe902a8c6847334edd0f597ef4672319 Mon Sep 17 00:00:00 2001 From: pavsoss Date: Tue, 23 Jun 2026 22:20:29 +0530 Subject: [PATCH] fix(auth): scope getPrCiStatus access to maintainer installation --- src/app/actions/maintainer.test.ts | 25 +++++++++++++++++++++++++ src/app/actions/maintainer.ts | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/src/app/actions/maintainer.test.ts b/src/app/actions/maintainer.test.ts index dd7c5ba6..f5fa88dd 100644 --- a/src/app/actions/maintainer.test.ts +++ b/src/app/actions/maintainer.test.ts @@ -16,6 +16,7 @@ import { getRepoPicker, setRepoManaged, resolveFlaggedAccount, + getPrCiStatus, } from './maintainer'; import * as detect from '@/lib/maintainer/detect'; import * as rateLimitLib from '@/lib/rate-limit'; @@ -764,4 +765,28 @@ describe('maintainer actions', () => { expect(c2.update).toHaveBeenCalledWith(expect.objectContaining({ status: 'dismissed' })); }); }); + + // getPrCiStatus + + describe('getPrCiStatus', () => { + it('returns not_authorised when install does not belong to user', async () => { + // mock assertMaintainerInstall failure (no junction row) + mockFrom.mockReturnValueOnce(chain(null)); + + const res = await getPrCiStatus(999, 'org/repo', 1); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('not_authorised'); + }); + + it('returns status when install belongs to user', async () => { + // mock assertMaintainerInstall success + mockFrom.mockReturnValueOnce(chain({ installation_id: 1 })); + + // Using demo/repo hits the fallback path without mocking Octokit + const res = await getPrCiStatus(1, 'demo/repo', 1); + + expect(res.ok).toBe(true); + }); + }); }); diff --git a/src/app/actions/maintainer.ts b/src/app/actions/maintainer.ts index 403edacb..5bcf4d45 100644 --- a/src/app/actions/maintainer.ts +++ b/src/app/actions/maintainer.ts @@ -783,6 +783,8 @@ export async function getPrCiStatus( ): Promise> { const sb = await getServerSupabase(); if (!sb) return err('not_configured', 'auth not configured'); + const service = getServiceSupabase(); + if (!service) return err('not_configured', 'service role missing'); const { data: { user }, @@ -793,6 +795,10 @@ export async function getPrCiStatus( return err('not_authorised', 'not a maintainer'); } + if (!(await assertMaintainerInstall(service, user.id, installationId))) { + return err('not_authorised', 'not your install'); + } + const cacheKey = `ci:status:${repoFullName}:${prNumber}`; const cached = await cacheGet<'passing' | 'failing' | 'pending' | null>(cacheKey); if (cached !== null) {