Description
Routes under /api/gt/repos/[owner]/[name]/ proxy GitHub API calls through the server's privileged PAT (GITHUB_PAT / GITHUB_PATS) without validating that the requested owner/name is in the tracked/allowed repo list. Any authenticated dashboard user can supply an arbitrary owner and repo name to read files, READMEs, or metadata from repos the PAT can access — including private repos the user was never meant to see.
Affected routes (all follow the same pattern):
| Route |
GitHub API call |
GET /api/gt/repos/[owner]/[name]/contents?path= |
repos.getContent — returns full file content |
GET /api/gt/repos/[owner]/[name]/readme |
repos.getReadme — returns full README markdown |
GET /api/gt/repos/[owner]/[name]/contributing |
repos.getContent on CONTRIBUTING.md |
GET /api/gt/repos/[owner]/[name]/health |
various repo metadata calls |
GET /api/gt/repos/[owner]/[name]/miners |
repos.listContributors or similar |
Steps to Reproduce
- Log in to the dashboard with any valid account.
- Send a GET request with an arbitrary repo not in the tracked list:
GET /api/gt/repos/<any-org>/<private-repo>/contents?path=.env
- The server fetches the file using its privileged PAT and returns the full content.
No special privileges required beyond a valid session cookie.
Expected Behavior
Requests for repos not in the tracked/allowed list should be rejected with 404 or 403. The server's PAT should only be used to fetch repos that are explicitly configured in the dashboard's repo list.
Actual Behavior
src/app/api/gt/repos/[owner]/[name]/contents/route.ts (lines 20–21) passes params.owner, params.name, and the raw ?path= query param directly to the GitHub API with no allowlist check:
const r = await withRotation((octokit) =>
octokit.rest.repos.getContent({ owner: params.owner, repo: params.name, path }),
);
The same unchecked pattern applies to every other route in the same directory. The only protection is the auth middleware (valid session required), but any legitimate dashboard user can exploit this.
Worst-case scenario: If GITHUB_PAT is a classic token with repo scope, the attacker can read any file from any private repo the token owner has access to — including .env files, credentials, and private source code across the entire organization.
Proposed Fix
Validate owner/name against the allowed repo list before proxying the GitHub API call. Add this check at the top of each affected route handler:
import { getLiveReposAsyncServer } from '@/lib/repos-server';
const { repos } = await getLiveReposAsyncServer();
const fullName = `${params.owner}/${params.name}`.toLowerCase();
const allowed = repos.some((r) => r.fullName.toLowerCase() === fullName);
if (!allowed) return NextResponse.json({ error: 'Not found' }, { status: 404 });
Alternatively, centralise the check in a shared helper to avoid repeating it across all five routes.
Environment
- Browser: N/A (API route)
- OS: N/A
- Node version: any
Additional Context
Description
Routes under
/api/gt/repos/[owner]/[name]/proxy GitHub API calls through the server's privileged PAT (GITHUB_PAT/GITHUB_PATS) without validating that the requestedowner/nameis in the tracked/allowed repo list. Any authenticated dashboard user can supply an arbitrary owner and repo name to read files, READMEs, or metadata from repos the PAT can access — including private repos the user was never meant to see.Affected routes (all follow the same pattern):
GET /api/gt/repos/[owner]/[name]/contents?path=repos.getContent— returns full file contentGET /api/gt/repos/[owner]/[name]/readmerepos.getReadme— returns full README markdownGET /api/gt/repos/[owner]/[name]/contributingrepos.getContentonCONTRIBUTING.mdGET /api/gt/repos/[owner]/[name]/healthGET /api/gt/repos/[owner]/[name]/minersrepos.listContributorsor similarSteps to Reproduce
No special privileges required beyond a valid session cookie.
Expected Behavior
Requests for repos not in the tracked/allowed list should be rejected with
404or403. The server's PAT should only be used to fetch repos that are explicitly configured in the dashboard's repo list.Actual Behavior
src/app/api/gt/repos/[owner]/[name]/contents/route.ts(lines 20–21) passesparams.owner,params.name, and the raw?path=query param directly to the GitHub API with no allowlist check:The same unchecked pattern applies to every other route in the same directory. The only protection is the auth middleware (valid session required), but any legitimate dashboard user can exploit this.
Worst-case scenario: If
GITHUB_PATis a classic token withreposcope, the attacker can read any file from any private repo the token owner has access to — including.envfiles, credentials, and private source code across the entire organization.Proposed Fix
Validate
owner/nameagainst the allowed repo list before proxying the GitHub API call. Add this check at the top of each affected route handler:Alternatively, centralise the check in a shared helper to avoid repeating it across all five routes.
Environment
Additional Context
src/app/api/gt/repos/[owner]/[name]/GITHUB_PAT/GITHUB_PATS) is the attack vector — severity scales directly with how broad its permissions are (classicrepo-scoped PAT = maximum impact)GET /api/v1/documents/<document_id>(IDOR / Broken Object-Level Authorization) #133 (IDOR on document downloads) and PR fix(auth): guard rejectUser against self-rejection and last-admin loc… #125 (admin auth guards)