fix(mcp): scope the static MCP token to an operator-configured repo allowlist - #2274
Conversation
…llowlist GITTENSORY_MCP_TOKEN is documented (README, CLI) as an ordinary end-user credential — an alternative to `gittensory-mcp login` for any miner or maintainer. But requireRepoManageAccess/requireRepoApprovalQueueAccess unconditionally trusted every non-session identity, so any holder of that one shared secret could propose, list, and decide pending merge/close/approve actions on every repo the App is installed on, not just repos they actually maintain. Scope the static `mcp` identity to a new MCP_ACTUATION_REPO_ALLOWLIST env var (comma/whitespace `owner/repo` list, or `*`/`all` to opt back into the old unscoped behavior). Unset defaults to deny-all — an operator must explicitly opt a repo in. The `api`/`internal` static identities are untouched: unlike `mcp`, those are operator-only Worker secrets never handed to end users. Advances #1936. Closes #2253.
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-01 17:11:51 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 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 Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2274 +/- ##
=======================================
Coverage 95.71% 95.71%
=======================================
Files 222 222
Lines 24661 24672 +11
Branches 8949 8956 +7
=======================================
+ Hits 23605 23616 +11
Misses 433 433
Partials 623 623
🚀 New features to boost your workflow:
|
Fix a real CI failure and the gate's documentation blocker: - typecheck: exactOptionalPropertyTypes rejects an explicit `MCP_ACTUATION_REPO_ALLOWLIST: undefined` on a Partial<Env> arg. createTestEnv's own fixture defaults this to "*" (so unrelated tests aren't broken by the new restriction), so the two "deny by default" tests need an explicit override -- use "" instead of undefined (isMcpActuationRepoAllowed treats both identically). The two "api/internal remain trusted" tests never reach that check at all, so they can omit the key. - docs: add the MCP_ACTUATION_REPO_ALLOWLIST env var to the self-host configuration docs alongside GITTENSORY_MCP_TOKEN, with deny-by-default and */all migration guidance for operators upgrading from the unscoped behavior.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | b3e2842 | Commit Preview URL Branch Preview URL |
Jul 01 2026, 01:12 PM |
…list (#2464) The shared, end-user-obtainable GITTENSORY_MCP_TOKEN had unconditional read access to every private repo, any other contributor's private decision pack/profile/notifications, and operator-only fleet analytics. #2274 scoped the write/manage MCP paths to MCP_ACTUATION_REPO_ALLOWLIST but left canAccessRepo, requireContributorAccess, and requireOperatorAccess trusting the static mcp identity unconditionally. Adds a MCP_READ_REPO_ALLOWLIST env var (fail-closed, same csv/wildcard model as the write-side allowlist) scoping repo-context reads, and a wildcard-only unlock for the non-repo-scoped contributor/operator tools since there's no single repo to check a scoped entry against. api and internal static identities (operator-only Worker secrets) remain unconditionally trusted, unaffected by this change. Fixes #2455
What
GITTENSORY_MCP_TOKENis documented (README, CLI) as an ordinary end-user credential — a normal alternative togittensory-mcp loginfor any miner or maintainer. ButrequireRepoManageAccess/requireRepoApprovalQueueAccessinsrc/mcp/server.tsunconditionally trusted every non-session identity (if (this.identity.kind !== "session") return;), so any holder of that one shared secret could callgittensory_propose_action/gittensory_list_pending_actions/gittensory_decide_pending_actionon every repo the App is installed on — not just repos they actually maintain.Fix
Scope the static
mcpidentity specifically to a newMCP_ACTUATION_REPO_ALLOWLISTenv var (comma/whitespaceowner/repolist, or*/allto explicitly opt back into the old unscoped behavior). Unset defaults to deny-all — an operator must explicitly opt a repo in.api/internalstatic identities are untouched — unlikemcp, those are operator-only Worker secrets that are never documented or handed out as an end-user credential, so the existing "static identities are trusted" design remains correct for them.Breaking change
Any self-hoster currently relying on
GITTENSORY_MCP_TOKENfor approval-queue actuation needs to setMCP_ACTUATION_REPO_ALLOWLIST(or*to keep today's behavior) after this ships. Given the severity of unscoped any-repo actuation via a single shared secret, deny-by-default is the correct posture — this isn't a regression to avoid, it's the fix.Tests
isMcpActuationRepoAlloweddirect unit tests: unset/empty → deny, explicit match → allow, sibling repo → deny, case-insensitive, comma/whitespace parsing,*/allwildcard.mcpcaller is denied onpropose_actionanddecide_pending_actionwhen the repo isn't allowlisted (and the pending action is left untouched, not silently accepted/rejected); allowed once the repo is explicitly listed; a sibling repo not in a narrower allowlist stays denied.api/internalstatic identities remaining unconditionally trusted on both gates (this branch was previously covered incidentally by the same identity the fix now scopes — added explicit tests so it isn't accidentally narrowed by a future change).Full unsharded
test:coveragegreen;typecheckgreen.Advances #1936. Closes #2253.