Configuration & Environment
Declaring overly broad OAuth scopes (e.g., `repo`, `admin:org`) when only read operations are invoked grants unnecessary write and admin privileges that an attacker can exploit through a compromised token.
Over-provisioned OAuth scopes occur when a client is configured with permissions far exceeding what the code actually exercises. In the GitHub OAuth model, `repo` grants full read/write control over private repositories, while `admin:org` grants complete organization administration — both far exceed what is needed for operations like `issues.listForRepo` or `repos.get`. This violates the principle of least privilege at the API authorization layer.
MCP servers wire OAuth-authenticated API clients directly to LLM-driven tool invocations, meaning a single prompt injection or tool-chaining attack that hijacks execution can immediately leverage whatever scopes are provisioned — with no human approval step between the LLM decision and the API call. Traditional web APIs are typically invoked by human-initiated requests with narrow, purpose-built tokens, while MCP tools are composed dynamically by models that may be manipulated into invoking destructive operations the developer never anticipated. A leaked or exfiltrated token from an MCP server process therefore carries the full blast radius of its declared scopes.
import { Octokit } from '@octokit/rest'; |
export function buildClient(token: string) { |
return new Octokit({ |
auth: token, |
scopes: ['repo', 'admin:org'], |
}); |
} |
export async function listIssues(octokit: Octokit, owner: string, repo: string) { |
const issues = await octokit.rest.issues.listForRepo({ owner, repo }); |
return issues.data.map((i) => ({ number: i.number, title: i.title })); |
} |
import { Octokit } from '@octokit/rest'; |
export function buildClient(token: string) { |
return new Octokit({ |
auth: token, |
scopes: ['public_repo', 'read:org'], |
}); |
} |
export async function listIssues(octokit: Octokit, owner: string, repo: string) { |
const issues = await octokit.rest.issues.listForRepo({ owner, repo }); |
return issues.data.map((i) => ({ number: i.number, title: i.title })); |
} |
MCPSafe performs same-file taint analysis pairing scope literal arrays in Octokit constructor calls (or equivalent Google API auth config objects) against the set of SDK method calls reachable within the file; if `repo` or `admin:org` appear in the scope array but no write or admin SDK methods (e.g., `issues.create`, `repos.delete`, `orgs.setMembershipForUser`) are invoked, the rule fires. Negative fixtures using already-minimal scopes (`public_repo`, `read:org`, `repo:status`) are explicitly excluded from matching.
See the full threat catalog for every documented detection.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now