Skip to content

Commit 9c51067

Browse files
committed
Fix admin auth bypass: enforce GitHub org-admin checks in all environments
Previously, authorizeOrgAccess() skipped all admin authorization in non-production environments, allowing any authenticated user to perform admin actions (update CLAs, toggle activation, manage bypass lists) and read admin panel data for any organization. Changes: - Remove blanket NODE_ENV !== "production" bypass from authorizeOrgAccess - Add scoped DB-admin fallback for non-production (only the designated installer passes, not any authenticated user), consistent with filterInstalledOrganizationsForAdmin - Restrict GET /api/sign/[orgSlug] to return only signing-relevant org fields (name, slug, avatar, isActive, claText) instead of the full org row which leaked adminUserId, installationId, etc. - Remove installedOrgsCount from GET /api/orgs response to prevent leaking total platform org count to non-admin users https://claude.ai/code/session_01FxG73b4jTBhGa5CK6kyKrG
1 parent 5a80a5c commit 9c51067

3 files changed

Lines changed: 16 additions & 10 deletions

File tree

app/api/orgs/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export async function GET() {
4141
return NextResponse.json({
4242
orgs,
4343
user: toSessionUserDto(user),
44-
installedOrgsCount: installedOrgs.length,
4544
})
4645
} catch (err) {
4746
console.error("Failed to list authorized organizations:", err)

app/api/sign/[orgSlug]/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export async function GET(
2828

2929
return NextResponse.json({
3030
org: {
31-
...org,
31+
name: org.name,
32+
githubOrgSlug: org.githubOrgSlug,
33+
avatarUrl: org.avatarUrl,
34+
isActive: org.isActive,
3235
claMarkdown: org.claText,
3336
},
3437
user: toSessionUserDto(user),

lib/server/org-access.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,21 @@ export async function authorizeOrgAccess(orgSlug: string): Promise<OrgAccessResu
3535
}
3636
}
3737

38-
if (process.env.NODE_ENV !== "production") {
39-
return { ok: true, org, user }
40-
}
41-
4238
try {
4339
const isAdmin = await isGitHubInstallationAccountAdmin(user, org)
4440
if (!isAdmin) {
45-
return {
46-
ok: false,
47-
status: 403,
48-
message: "Forbidden: GitHub installation admin access required",
41+
// In non-production, when no GitHub OAuth token is available for org-admin
42+
// checks, fall back to the DB admin mapping — consistent with
43+
// filterInstalledOrganizationsForAdmin. This is strictly scoped: only the
44+
// designated admin (who installed the app) passes, not any authenticated user.
45+
const hasDbAdminFallback =
46+
process.env.NODE_ENV !== "production" && org.adminUserId === user.id
47+
if (!hasDbAdminFallback) {
48+
return {
49+
ok: false,
50+
status: 403,
51+
message: "Forbidden: GitHub installation admin access required",
52+
}
4953
}
5054
}
5155
} catch (error) {

0 commit comments

Comments
 (0)