From a5416bd9fed95ee1a882d0799375cba2849e5df1 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Fri, 26 Jun 2026 03:52:39 -0700 Subject: [PATCH] fix(mcp): gate maintainer noise behind maintainer access --- src/mcp/server.ts | 2 +- test/unit/mcp-output-schemas.test.ts | 37 ++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index ecce1c4289..55b36337dc 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -1821,7 +1821,7 @@ export class GittensoryMcp { private async getMaintainerNoise(input: { owner: string; repo: string }): Promise { const fullName = `${input.owner}/${input.repo}`; - await this.requireRepoAccess(fullName); + await this.requireRepoApprovalQueueAccess(fullName); const report = await loadMaintainerNoiseReport(this.env, fullName); return { summary: maintainerNoiseSummary(report), diff --git a/test/unit/mcp-output-schemas.test.ts b/test/unit/mcp-output-schemas.test.ts index 4db0eb5c4c..5a92a925bf 100644 --- a/test/unit/mcp-output-schemas.test.ts +++ b/test/unit/mcp-output-schemas.test.ts @@ -2,6 +2,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { describe, expect, it } from "vitest"; import { persistSignalSnapshot, upsertBounty, upsertIssueFromGitHub, upsertPullRequestFromGitHub, upsertRepositoryFromGitHub, updatePullRequestSlopAssessment } from "../../src/db/repositories"; +import type { AuthIdentity } from "../../src/auth/security"; import { GittensoryMcp } from "../../src/mcp/server"; import { normalizeRegistryPayload } from "../../src/registry/normalize"; import { persistRegistrySnapshot } from "../../src/registry/sync"; @@ -30,8 +31,8 @@ const TOOLS_WITH_OUTPUT_SCHEMA = [ "gittensory_explain_score_breakdown", ]; -async function connectTestClient(env: Env = createTestEnv()) { - const mcpServer = new GittensoryMcp(env).createServer(); +async function connectTestClient(env: Env = createTestEnv(), identity?: AuthIdentity) { + const mcpServer = new GittensoryMcp(env, identity).createServer(); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await mcpServer.connect(serverTransport); const client = new Client({ name: "gittensory-output-schema-test", version: "0.1.0" }, { capabilities: {} }); @@ -200,6 +201,38 @@ describe("MCP tool calls return schema-valid structured content", () => { expect(JSON.stringify(data)).not.toMatch(/hotkey|coldkey|wallet|payout|reward/i); }); + it("gittensory_get_maintainer_noise denies cached member-only session access", async () => { + const env = createTestEnv(); + await upsertRepositoryFromGitHub(env, { name: "private-repo", full_name: "victim-org/private-repo", private: true, owner: { login: "victim-org" }, default_branch: "main" }); + await upsertPullRequestFromGitHub(env, "victim-org/private-repo", { + number: 7, + title: "cached member evidence", + state: "open", + user: { login: "read-only-member" }, + author_association: "MEMBER", + body: "", + }); + const { client } = await connectTestClient(env, { + kind: "session", + actor: "read-only-member", + session: { + id: "session-read-only-member", + tokenHash: "hash", + login: "read-only-member", + scopes: [], + expiresAt: "2999-01-01T00:00:00.000Z", + createdAt: "2026-01-01T00:00:00.000Z", + metadata: {}, + }, + }); + + const result = await client.callTool({ name: "gittensory_get_maintainer_noise", arguments: { owner: "victim-org", repo: "private-repo" } }); + + expect(result.isError).toBe(true); + expect(JSON.stringify(result.content)).toContain("maintainer access is required"); + expect(result.structuredContent).toBeUndefined(); + }); + it("gittensory_validate_linked_issue reports multiplier eligibility for an uncached issue", async () => { const { client } = await connectTestClient(); const result = await client.callTool({ name: "gittensory_validate_linked_issue", arguments: { owner: "octo", repo: "demo", issueNumber: 1 } });