Skip to content

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

Description

@JSONbored

Parent: #1936

Problem

requireRepoManageAccess and requireRepoApprovalQueueAccess (src/mcp/server.ts:1782, 1803) both return immediately — skipping every repo-scoped permission check — when this.identity.kind !== "session". authenticatePrivateToken (src/auth/security.ts:106) maps any caller presenting the single global Worker secret GITTENSORY_MCP_TOKEN to { kind: "static", actor: "mcp" }. This token is documented in packages/gittensory-mcp/README.md:186 and the CLI (bin/gittensory-mcp.js:1838,2424,3065-3067) as a normal alternative to gittensory-mcp login — nothing marks it operator-only, so any user (miner or maintainer) can plausibly export it locally believing it's their own credential.

decidePendingAction (src/mcp/server.ts:2497-2500) confirms the consequence precisely: after the no-op requireRepoApprovalQueueAccess call, it only scopes an action ID to the requested repo (pending.repoFullName !== fullName) — it never restricts which repoFullName a static-identity caller is allowed to pass in the first place.

Failure scenario: anyone who sets export GITTENSORY_MCP_TOKEN=<value> and points an MCP client at the deployed server can call gittensory_decide_pending_action to accept (execute) a staged merge/close/approve on a repo they have no GitHub write access to and are not a maintainer of, or call gittensory_propose_action to stage new actuation into that repo's queue.

Requirements

  • A static-token identity must be scoped to the repos the operator actually intends it to control — not implicitly trusted for every installed repo.
  • The fix must be config-driven (operator-configurable scope), not a hardcoded allowlist, since self-hosters run this against many different repo sets.

Deliverables

  • Remove the unconditional early return in requireRepoManageAccess/requireRepoApprovalQueueAccess for non-session identities; require an explicit, operator-configured repo scope for static-token callers (e.g. an env-configured allowlist of repos the static token may act on, or a stronger per-token-per-repo credential model).
  • At minimum, ensure decidePendingAction/proposeAction validate the static caller's permitted repo scope before touching any repo's pending-action queue, not just before touching a specific already-known action ID.
  • Update the MCP README/CLI docs so GITTENSORY_MCP_TOKEN is clearly described as an operator-scoped credential, not a general end-user login alternative, if the broader authorization model isn't changed.
  • Add a regression test asserting a static-identity caller is denied gittensory_decide_pending_action/gittensory_propose_action against a repo outside its configured scope.

Acceptance criteria

  • A static-token caller can only propose/list/decide pending actions for repos explicitly within its configured scope.
  • Session-based (per-user, GitHub-authenticated) access is unaffected.

Expected outcome

The MCP actuation surface can no longer be reached fleet-wide by any holder of one shared secret — access is scoped the same way every other actuation entrypoint already is.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions