Skip to content

fix(mcp): scope the static MCP token to an operator-configured repo allowlist - #2274

Merged
JSONbored merged 4 commits into
mainfrom
claude/mcp-static-token-repo-scope
Jul 1, 2026
Merged

fix(mcp): scope the static MCP token to an operator-configured repo allowlist#2274
JSONbored merged 4 commits into
mainfrom
claude/mcp-static-token-repo-scope

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

What

GITTENSORY_MCP_TOKEN is documented (README, CLI) as an ordinary end-user credential — a normal alternative to gittensory-mcp login for any miner or maintainer. But requireRepoManageAccess/requireRepoApprovalQueueAccess in src/mcp/server.ts unconditionally trusted every non-session identity (if (this.identity.kind !== "session") return;), so any holder of that one shared secret could call gittensory_propose_action / gittensory_list_pending_actions / gittensory_decide_pending_action on every repo the App is installed on — not just repos they actually maintain.

Fix

Scope the static mcp identity specifically to a new MCP_ACTUATION_REPO_ALLOWLIST env var (comma/whitespace owner/repo list, or */all to explicitly opt back into the old unscoped behavior). Unset defaults to deny-all — an operator must explicitly opt a repo in.

api/internal static identities are untouched — unlike mcp, 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_TOKEN for approval-queue actuation needs to set MCP_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

  • isMcpActuationRepoAllowed direct unit tests: unset/empty → deny, explicit match → allow, sibling repo → deny, case-insensitive, comma/whitespace parsing, */all wildcard.
  • MCP integration tests: a static mcp caller is denied on propose_action and decide_pending_action when 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.
  • Regression coverage for api/internal static 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:coverage green; typecheck green.

Advances #1936. Closes #2253.

…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.
@dosubot dosubot Bot added the size:M label Jul 1, 2026
@JSONbored JSONbored self-assigned this Jul 1, 2026
@loopover-orb

loopover-orb Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-01 17:11:51 UTC

7 files · 1 AI reviewer · no blockers · readiness 75/100 · CI green · clean

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review

Review summary
The change correctly scopes only the static MCP actor through a deny-by-default repo allowlist while preserving the existing trusted behavior for api/internal static identities. The helper is simple, case-insensitive, and covered by direct and integration tests for denied, allowed, wildcard, and sibling-repo cases. The main remaining gap in the visible diff is rollout/documentation completeness for a breaking operator-facing env contract outside the new UI docs page.

Nits — 6 non-blocking
  • nit: apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx:77 documents the new breaking env var in the UI docs, but the visible diff does not show matching README or CLI/help text updates; I could not verify those files from the provided content, so confirm the documented GITTENSORY_MCP_TOKEN paths now mention MCP_ACTUATION_REPO_ALLOWLIST.
  • nit: test/unit/mcp-automation-state.test.ts:211 says createTestEnv({}) has the allowlist unset, but test/helpers/d1.ts now defaults MCP_ACTUATION_REPO_ALLOWLIST to "*", so the comment should be corrected to avoid misleading future maintainers.
  • apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx:77: mirror this warning in the README and any CLI/env reference that currently presents GITTENSORY_MCP_TOKEN as a normal MCP credential, or point to those existing updates if they are in another file.
  • test/unit/mcp-automation-state.test.ts:211: change the comment to say the api/internal test proves those actors ignore the MCP allowlist despite the test default wildcard, or override MCP_ACTUATION_REPO_ALLOWLIST to "" for a clearer assertion.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ Linked #2253
Related work ⚠️ 2 scoped overlaps Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (size label size:M; 1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 67 registered-repo PR(s), 57 merged, 590 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 67 PR(s), 590 issue(s).
Gate result ⚠️ Not blocking Advisory; not blocking this PR.
Review context
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Review top overlaps.
  • Add a concise scope and risk note.
  • Triage stale or unlinked PRs.
  • No action.
  • Check active issues and PRs before submitting.
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.

🟩 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.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added gittensor gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. labels Jul 1, 2026
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.71%. Comparing base (17d70b2) to head (b3e2842).
✅ All tests successful. No failed tests found.

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           
Files with missing lines Coverage Δ
src/auth/security.ts 98.76% <100.00%> (+0.08%) ⬆️
src/mcp/server.ts 95.76% <100.00%> (+0.03%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JSONbored added 3 commits July 1, 2026 04:25
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.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 1, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@JSONbored
JSONbored merged commit 3eb3a49 into main Jul 1, 2026
14 checks passed
@JSONbored
JSONbored deleted the claude/mcp-static-token-repo-scope branch July 1, 2026 19:10
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 1, 2026
JSONbored added a commit that referenced this pull request Jul 2, 2026
…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
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

fix(mcp): a static GITTENSORY_MCP_TOKEN grants actuation access to every repo, not just the operator's

1 participant