Skip to content

feat(auth): replace the global admin allowlist with a live per-repo permission check for hosted deployments - #8063

Merged
JSONbored merged 1 commit into
JSONbored:mainfrom
cleanjunc:feat/4889-per-repo-admin
Jul 22, 2026
Merged

feat(auth): replace the global admin allowlist with a live per-repo permission check for hosted deployments#8063
JSONbored merged 1 commit into
JSONbored:mainfrom
cleanjunc:feat/4889-per-repo-admin

Conversation

@cleanjunc

Copy link
Copy Markdown
Contributor

Summary

Closes #4889.

Implements the ratified decision (2026-07-21) exactly: a live GitHub permission check replaces the single global ADMIN_GITHUB_LOGINS allowlist at every listed fleet-operator exemption site, for hosted deployments only — self-host keeps the global allowlist as its default, byte-identical.

  • The helper (src/auth/security.ts, the file that owns parseGitHubLoginList): isPerTenantAdmin(env, installationId, repoFullName, login) — per-repo admin mode OFF (default): exact ADMIN_GITHUB_LOGINS membership, unchanged semantics; mode ON: GET /repos/{owner}/{repo}/collaborators/{username}/permission via the existing getRepositoryCollaboratorPermission (src/github/app.ts) — admin/maintain passes, anything else denies. Fail-closed on every can't-verify path (no installation to ask through, an API error, an unknown collaborator), per the issue's explicit guardrail — an API blip must never silently grant fleet-operator trust. The mode flag is LOOPOVER_PER_REPO_ADMIN (truthy-string, OFF by default, declared in wrangler.jsonc vars + env.d.ts; worker-configuration.d.ts regenerated via cf-typegen).
  • src/queue/processors.ts (7 sites + 1 special): the contributor-cap exemptions (PR open + issue paths), the agent-maintenance close-eligibility computation, the account-age labeling exemption, the manual-review freeze exemption, both comment-throttle guards (review-nag, monitored mentions), and authorizeFeedbackActor (now async, threading the webhook's installation id) all consult the helper. The visual follow-up notify set is the one non-mechanical site: live permissions cannot be enumerated (the API answers per-login queries only), so in per-repo admin mode the allowlist contributes no notify logins — the repo owner + the repo's own configured bug_analysis_notify list remain the notify surface (documented at the site, pinned by test).
  • src/queue/review-evasion.ts (3 sites) and src/review/linked-issue-label-propagation-fetch.ts: these sites already follow their allowlist shortcut with a live collaborator check — in per-repo admin mode the shortcut simply stops granting and that existing live check (already fail-safe) becomes the sole non-owner permission source. The repo-owner shortcut is untouched everywhere (it predates and is independent of the allowlist this replaces, mirroring the login === repoOwner precedent the issue cites).

Out of scope, deliberately: the session-path gates (canSessionAccessPath, MCP) — they carry no repo context to be per-repo about and are not in the issue's confirmed call-site list.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; codecov/patch requires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • Instead of the full unsharded test:coverage (30+ min on this machine), I ran every affected suite with coverage — the new auth-per-repo-admin + review-evasion-per-repo-admin files, plus linked-issue-label-propagation-fetch, queue-2/3/4/5, global-contributor-cap, account-age-throttle, visual-followup, auth-security-helpers (655 tests, all green) — and measured patch coverage empirically by intersecting coverage/lcov.info DA/BRDA records with git diff -U0 against upstream/main: 100% of changed lines and 100% of changed branches in every changed src/** file.
  • actionlint (no workflow change), test:workers/build:mcp/test:mcp-pack/ui:* (no workers/mcp/UI change — the diff is backend src/** + wrangler.jsonc/worker-configuration.d.ts/env.d.ts typegen), npm audit (zero dependency changes; the current moderate findings exist on upstream/main itself).

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

The two unchecked Safety boxes are N/A: no UI change of any kind (backend-only diff; zero apps/** files touched).

Negative paths pinned by test: mode ON fails closed on a thrown permission lookup (Error and non-Error), on a null installation id, and on an unknown collaborator; a blank login is denied in either mode; the tests assert by observed API traffic that mode OFF makes zero permission calls for allowlisted actors and that mode ON actually consults the live endpoint where the allowlist used to short-circuit.

UI Evidence

N/A — no UI change (backend only).

Notes

  • Default-off invariant: with LOOPOVER_PER_REPO_ADMIN unset (every existing deployment), every converted site resolves the identical allowlist answer with zero extra API calls — pinned by the mode-OFF tests asserting empty fetch traffic. The flag flips the permission source, not any site's control flow.
  • The issue's own framing ("no new table, no admin UI") is honored: no storage, no cache, no enumeration — each check is a point query against GitHub's live answer, mirroring the hasMaintainerOrOwnerPermission pattern the codebase already trusts on the review-evasion paths.

@cleanjunc
cleanjunc requested a review from JSONbored as a code owner July 22, 2026 17:55
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 22, 2026
@loopover-orb

loopover-orb Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-22 18:18:23 UTC

11 files · no blockers · CI green · clean

⏸️ Suggested Action - Manual Review

  • AI review already in progress for this PR head: Another LoopOver pass is already running the AI review for this exact PR head. This pass is skipping to avoid a duplicate LLM call.

Review summary
AI review is already running for this PR head in another LoopOver pass. LoopOver is holding this PR for manual review until that pass completes.

Nits — 1 non-blocking
  • AI review already in progress for this PR head — The gate is held for a human reviewer rather than passed automatically; it re-evaluates once the in-flight review completes or on the next update.

Decision drivers

  • ✅ Code review — No blockers (No AI review summary)
  • ⚠️ Gate result — Not blocking (Advisory; not blocking this PR.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #4889
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 47 registered-repo PR(s), 13 merged, 33 issue(s).
Contributor context ✅ Confirmed Gittensor contributor cleanjunc; Gittensor profile; 47 PR(s), 33 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The diff adds isPerTenantAdmin (mode-gated, fail-closed on missing installation/API error/unknown collaborator) and threads it through all listed call sites in processors.ts, review-evasion.ts, and linked-issue-label-propagation-fetch.ts, while leaving self-host's default ADMIN_GITHUB_LOGINS behavior byte-identical when the mode flag is off. It includes explicit tests for an admin/maintain pass, a

Review context
  • Author: cleanjunc
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 47 PR(s), 33 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 22, 2026
@JSONbored
JSONbored merged commit dbf54e3 into JSONbored:main Jul 22, 2026
10 checks passed
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.29%. Comparing base (f3cb090) to head (7b36193).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8063      +/-   ##
==========================================
+ Coverage   91.82%   92.29%   +0.47%     
==========================================
  Files         733      650      -83     
  Lines       75017    54366   -20651     
  Branches    23035    19397    -3638     
==========================================
- Hits        68883    50179   -18704     
+ Misses       5034     3312    -1722     
+ Partials     1100      875     -225     
Flag Coverage Δ
rees ?
shard-1 59.95% <37.50%> (+4.39%) ⬆️
shard-2 48.08% <29.16%> (-4.88%) ⬇️
shard-3 50.70% <87.50%> (-2.87%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/auth/security.ts 99.01% <100.00%> (+0.13%) ⬆️
src/queue/processors.ts 95.78% <100.00%> (+<0.01%) ⬆️
src/queue/review-evasion.ts 94.59% <100.00%> (ø)
src/review/linked-issue-label-propagation-fetch.ts 100.00% <100.00%> (ø)

... and 86 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace the single global admin allowlist with a per-customer concept

2 participants