Skip to content

fix(mcp): scope GITTENSORY_MCP_TOKEN read access to an operator allowlist - #2464

Merged
JSONbored merged 1 commit into
mainfrom
fix/mcp-static-token-read-scope-2455
Jul 2, 2026
Merged

fix(mcp): scope GITTENSORY_MCP_TOKEN read access to an operator allowlist#2464
JSONbored merged 1 commit into
mainfrom
fix/mcp-static-token-read-scope-2455

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • The shared, end-user-obtainable GITTENSORY_MCP_TOKEN (static mcp identity) had unconditional read access to every private repo, any other contributor's private decision pack/profile/notifications, and operator-only fleet analytics. fix(mcp): scope the static MCP token to an operator-configured repo allowlist #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 MCP_READ_REPO_ALLOWLIST (fail-closed, same csv/wildcard model as the write-side allowlist) scoping repo-context reads (gittensory_get_repo_context, gittensory_get_issue_quality, gittensory_preflight_pr, gittensory_validate_linked_issue, gittensory_get_label_audit, etc.), and requires the full wildcard opt-in for the non-repo-scoped contributor/operator tools (another contributor's decision pack/profile/notifications, gittensory_get_fleet_analytics) since there's no single repo to check a scoped allowlist entry against.
  • api/internal static identities (operator-only Worker secrets, never handed to end users) remain unconditionally trusted, unaffected by this change.
  • requireWatchableRepo was left unchanged: its only caller (watchIssues) already gates the static mcp identity via requireContributorAccess's stricter wildcard-only check first, so an additional repo-scoped check there would be unreachable dead code.

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 an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; new code is 100% line+branch covered (verified via coverage/lcov.info for the exact ranges touched). Global 96.54%/95.51%.
  • 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 — 0 vulnerabilities
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Also ran npm run db:migrations:check and npm run rees:test (unaffected, both green) since this repo's full test:ci includes them.

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. (Added deny-by-default + wildcard-opt-in + api/internal-still-trusted regression tests across test/unit/issue-watch.test.ts, test/unit/mcp-fleet-analytics.test.ts, test/unit/mcp-output-schemas.test.ts.)
  • API/OpenAPI/MCP behavior is updated and tested where needed. No OpenAPI surface changed (this is MCP-internal auth, not a REST route).
  • UI changes use live API data or real empty/error/loading states — N/A, no UI changed.
  • Visible UI changes include a UI Evidence section — N/A, no UI changed.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs. (No changelog edit; env var is self-documented via the env.d.ts doc comment, matching how MCP_ACTUATION_REPO_ALLOWLIST is documented.)

Notes

…list

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
@dosubot dosubot Bot added the size:M label Jul 2, 2026
@loopover-orb

loopover-orb Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

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

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-02 05:09:13 UTC

7 files · 1 AI reviewer · no blockers · readiness 86/100 · CI pending · blocked

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review

Review summary
This change cleanly separates read-side MCP trust from actuation trust and routes the static mcp identity through a fail-closed read allowlist while preserving api/internal static trust. The repo-scoped path is centralized in canAccessRepo, and the unscoped contributor/operator paths require an explicit wildcard, which matches the stated security boundary. The added tests cover the main deny/default and allowlisted cases without fabricating an impossible path.

Nits — 5 non-blocking
  • nit: test/helpers/d1.ts:100 makes MCP_READ_REPO_ALLOWLIST default to "*", which preserves existing tests but also makes future tests opt out manually before they can exercise production's fail-closed default.
  • nit: src/auth/security.ts:178 documents watch subscriptions as repo-scoped read tools even though gittensory_watch_issues is intentionally gated through the unscoped wildcard path; tightening that wording would avoid future maintainers adding a weaker repo-scoped bypass.
  • src/auth/security.ts:178: clarify the comment so repo-context/issue-quality style tools are described as repo-scoped, while watch subscription management is explicitly called out as requiring isMcpReadUnscoped.
  • test/unit/mcp-output-schemas.test.ts:208: add a case-insensitivity/whitespace test for MCP_READ_REPO_ALLOWLIST if this parser is expected to mirror MCP_ACTUATION_REPO_ALLOWLIST exactly.
  • 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 ⚠️ Missing No linked issue or no-issue rationale found.
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 (size label size:M; no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 65 registered-repo PR(s), 55 merged, 554 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 65 PR(s), 554 issue(s).
Gate result ⚠️ Not blocking Advisory; not blocking this PR.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 65 PR(s), 554 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Triage stale or unlinked PRs.
  • No action.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
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 2, 2026
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.93%. Comparing base (f9662a7) to head (8a9a692).
⚠️ Report is 14 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2464   +/-   ##
=======================================
  Coverage   95.93%   95.93%           
=======================================
  Files         225      225           
  Lines       25332    25343   +11     
  Branches     9217     9221    +4     
=======================================
+ Hits        24302    24313   +11     
  Misses        417      417           
  Partials      613      613           
Files with missing lines Coverage Δ
src/auth/security.ts 98.83% <100.00%> (+0.07%) ⬆️
src/mcp/server.ts 95.80% <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 JSONbored self-assigned this Jul 2, 2026
@JSONbored
JSONbored merged commit 604e287 into main Jul 2, 2026
12 checks passed
@JSONbored
JSONbored deleted the fix/mcp-static-token-read-scope-2455 branch July 2, 2026 05:30
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 2, 2026
@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.

1 participant