Tinker AI
Read reviews
advanced 4 min read AI-assisted

Auditing your MCP config for leaked secrets

Published 2026-05-20 by Owner

According to GitGuardian’s 2026 report, 24,008 secrets are sitting in MCP configuration files on public GitHub, 2,117 of them valid. This guide is the audit that keeps your repositories out of next year’s number. Work top to bottom; rotation comes before cleanup if you find anything live.

Where MCP secrets live

Check every path that a tool might write a config to, not just the project root:

~/.config/mcp/
~/.cursor/mcp.json
~/.claude/mcp.json
~/Library/Application Support/Claude/claude_desktop_config.json
./.cursor/mcp.json
./.mcp.json

Project-local files are the ones that end up committed; user-level files leak through dotfile repos and shared machine images.

Detect committed config in history

A file removed from the current tree can still live in history. Search the whole history, not just the working copy:

git log --all --full-history --diff-filter=A -- '**/mcp.json' '**/.mcp.json' '**/claude_desktop_config.json'

Then scan for inline secret shapes across tracked files:

rg -n '("(api[_-]?key|token|secret|authorization)"\s*:\s*")[A-Za-z0-9_\-]{16,}' --glob '*.json'

Add MCP paths to a secret scanner

Run gitleaks against the full history with the MCP paths in scope:

gitleaks detect --source . --log-opts="--all" --redact

For continuous coverage, add the config paths to your scanner’s path allowlist so future commits are checked, and wire the scan into CI so a hardcoded key fails the build instead of merging.

Convert inline keys to environment variables

The fix is to reference the secret instead of embedding it. Replace this shape:

{
  "mcpServers": {
    "search": {
      "command": "search-mcp",
      "env": { "API_KEY": "sk-live-9f3a2c7b8e1d4456a0b2c3d4e5f6a7b8" }
    }
  }
}

with this one:

{
  "mcpServers": {
    "search": {
      "command": "search-mcp",
      "env": { "API_KEY": "${env:SEARCH_MCP_API_KEY}" }
    }
  }
}

and set SEARCH_MCP_API_KEY in your shell profile or secret manager, never in the committed file.

Rotate first, audit second

If a scan finds a live key, rotate it before you do anything else — a key in public history is compromised regardless of whether you later rewrite the history:

git log --all --source -- '**/mcp.json' | head

Revoke the exposed credential at the provider, issue a new one, update the environment variable, and only then clean the history.

Scope keys so a leak is survivable

Detection and rotation shorten how long a key is exposed; scoping limits what an exposed key can do. Issue MCP server credentials with the narrowest scope the tool actually needs, and prefer short-lived tokens over long-lived keys wherever the provider supports them. A read-only, single-resource token that leaks is an incident you close with one rotation; an account-wide key that leaks is a breach.

Where the provider supports per-token scopes, set them explicitly instead of accepting the default:

provider tokens create --name mcp-search --scope read:search --expires 30d

Store only that scoped token in the environment variable the config references. If a later audit finds it in a public repo, the blast radius is one resource for thirty days, not your whole account indefinitely.

Stop it recurring

Add the config paths to .gitignore and ship a committed example file with no real values:

echo ".cursor/mcp.json" >> .gitignore
echo ".mcp.json" >> .gitignore
echo "claude_desktop_config.json" >> .gitignore

Add a pre-commit hook that blocks the inline pattern:

pre-commit run gitleaks --all-files

For the broader runtime posture this audit sits inside, see hardening your agent supply chain; for why the format makes this the default failure, your agent config is leaking secrets.