Context
Part of the review-stack architecture audit (parent epic — hardcoding dimension). This is the clearest
"a self-hoster deploying their own fork silently can't use a generic-looking route" finding.
src/github/public.ts:54-55,153:
const PUBLIC_REPO_STATS_OWNER = "jsonbored";
const PUBLIC_REPO_STATS_REPO = "gittensory";
...
if (normalizedOwnerName !== PUBLIC_REPO_STATS_OWNER || normalizedRepoName !== PUBLIC_REPO_STATS_REPO)
throw new Error("invalid_github_repo");
Caller: src/api/routes.ts:962-971 mounts this as GET /v1/public/github/repos/:owner/:repo/stats — a
generically-parameterized route (unauthenticated, publicly cached) that looks like it serves stats
for any repo, but the implementation hard-rejects everything except one literal repo. Compare the sibling
route one block below it, /v1/public/repos/:owner/:repo/badge.svg
(loadPublicRepoBadge, routes.ts:976-988), which correctly works for any installed repo, gated
only by a per-repo opt-in setting — not a hardcoded owner/repo allowlist. A self-hoster deploying their
own fork and hitting their own /v1/public/github/repos/<their-owner>/<their-repo>/stats gets a hard 400
invalid_github_repo, even though the URL shape and route wiring say this should be generic.
Fix
Replace the two constants with an env-driven allowlist (or drop the allowlist and validate only
owner/repo syntax, matching badge.svg's pattern), e.g. PUBLIC_REPO_STATS_ALLOWLIST
(comma-separated owner/repo, empty = allow any — mirroring GITTENSORY_PUBLIC_STATS_REPOS's shape in
src/review/public-stats.ts).
Acceptance criteria
Context
Part of the review-stack architecture audit (parent epic — hardcoding dimension). This is the clearest
"a self-hoster deploying their own fork silently can't use a generic-looking route" finding.
src/github/public.ts:54-55,153:Caller:
src/api/routes.ts:962-971mounts this asGET /v1/public/github/repos/:owner/:repo/stats— agenerically-parameterized route (unauthenticated, publicly cached) that looks like it serves stats
for any repo, but the implementation hard-rejects everything except one literal repo. Compare the sibling
route one block below it,
/v1/public/repos/:owner/:repo/badge.svg(
loadPublicRepoBadge,routes.ts:976-988), which correctly works for any installed repo, gatedonly by a per-repo opt-in setting — not a hardcoded owner/repo allowlist. A self-hoster deploying their
own fork and hitting their own
/v1/public/github/repos/<their-owner>/<their-repo>/statsgets a hard 400invalid_github_repo, even though the URL shape and route wiring say this should be generic.Fix
Replace the two constants with an env-driven allowlist (or drop the allowlist and validate only
owner/repo syntax, matching
badge.svg's pattern), e.g.PUBLIC_REPO_STATS_ALLOWLIST(comma-separated
owner/repo, empty = allow any — mirroringGITTENSORY_PUBLIC_STATS_REPOS's shape insrc/review/public-stats.ts).Acceptance criteria