Your MCP config is a plaintext secrets file.
Every official MCP quickstart teaches the same pattern: paste your API key into the config JSON. It works instantly. It also stores live credentials in plaintext on disk, readable by every process on the machine and one accidental git add . away from being public. Keelix finds these secrets and flags them — without a vault, without a SaaS platform, and without the secrets ever leaving your box.
Where secrets end up in MCP config
Claude Code, Cursor, Windsurf, and other AI agents load MCP server definitions from JSON config files. The most common pattern, copied from official documentation, looks like this: each server entry includes an env block, and the API key is typed directly into that block as a string literal.
The file sits on disk in the user's home directory or in the project root. Any process running as that user — the agent itself, a malicious MCP server, a rogue npm postinstall script, a compromised dependency — can read it. If the project uses a dotfile manager or the config is included in a backup, the key propagates further. And if someone runs git add . in a directory containing the config, the credential goes into version control history.
// ~/.claude/claude_desktop_config.json
{
"mcpServers": {
"my-database-tool": {
"command": "npx",
"args": ["-y", "some-mcp-server"],
"env": {
"DATABASE_URL": "postgresql://user:PASSWORD@host/db",
"OPENAI_API_KEY": "sk-proj-..."
}
}
}
}How widespread is this?
The pattern is not an edge case. MCP servers frequently aggregate credentials for multiple services — a single config file for an agent with database access, email access, and an external API might contain three or four live secrets. Most of these files are created by following official documentation to the letter.
The problem compounds because MCP adoption moves faster than secrets hygiene tooling. Standard pre-commit secret scanners and CI/CD secret detection tools were not written to parse the specific structure of MCP config files, so the keys slip through.
The attack surfaces
Plaintext secrets in MCP config create risk across three distinct surfaces — each independent, any one sufficient.
Version control leakage
A single git add . in a directory containing a config file puts live credentials into history. Public repos are scanned by automated tools within minutes of a push — GitGuardian, Trufflehog, and GitHub's own push protection all watch for this. Rotating the key is mandatory; git history scrubbing is required.
LLM context injection
The agent that reads the config file has the file's contents in context — including the credentials. A malicious website, document, or MCP tool output embedding an exfiltration instruction can direct the agent to transmit those values. This attack is especially dangerous when combined with agent auto-approval.
Rogue MCP server read
Any MCP server running on the box executes as the user. A server with a supply-chain compromise, a rogue package installed via npm/pip, or a server whose tool descriptions contain injection patterns can read the config file and exfiltrate its contents through a legitimate-looking tool call.
Remediation: fix the config, not just the symptom
The right fix is to remove credentials from the config file entirely — not to obscure them. There are two patterns that work:
Use the ${env:VAR_NAME} syntax to reference an environment variable already set in your shell profile (~/.zshrc, ~/.bashrc). The config file no longer contains the value itself — only a reference to it. This pattern keeps secrets out of version control while requiring no additional tooling.
"env": {
"OPENAI_API_KEY": "${env:OPENAI_API_KEY}"
}Remote MCP servers that implement OAuth 2.1 handle authentication through a browser flow — no token is ever written to a config file. This is the strongest option for server-to-server integrations. Not all MCP servers support it yet, but adoption is growing as the MCP authorization spec matures.
Rotate the secret immediately — assume it has been indexed. Do not delete the commit and push a new one; the credential is in history. Use git-filter-repo to rewrite history, then force-push and notify all collaborators to re-clone. Private repos are not safe to skip this step — tokens can be harvested via repository access, compromised collaborator accounts, or leaked deploy keys.
What Keelix checks — and what it does not do
Keelix is not a secrets vault and does not sell one. It finds plaintext credentials in MCP config files, flags them as a posture finding, and tells you exactly which file and which key type is affected. That is the complete scope.
When Keelix discovers a secret, it redacts the value at the scan boundary before writing any output. The finding in the report records the config path and inferred credential type — not the credential value. Nothing is transmitted.
The plaintext-secrets check is one of four MCP risk categories Keelix grades. The others are agent auto-approval, the lethal trifecta, and unvetted MCP servers. Together they form the AI/MCP component of the whole-box 0–100 posture score you can gate CI on.
For a full view of what Keelix covers on the MCP surface, see the MCP Security overview.
Frequently asked
- Where does Claude Code store secrets?
- Claude Code reads MCP server configuration from several files: ~/.claude/claude_desktop_config.json (global), .claude/settings.json (project-level), and .mcp.json at the repository root. API keys, tokens, and database connection strings are commonly passed as environment variables inside the mcpServers JSON object — which means they sit in plaintext on disk, readable by any process running as the same user. Keelix scans all of these paths when it runs.
- Why do MCP config files contain plaintext secrets?
- Most official MCP setup guides recommend passing API keys directly as environment variables inside the server config JSON. The pattern works immediately and is simple to follow. The problem is that this normalizes storing live credentials in files that users frequently commit to version control, sync via dotfile managers, or share as project scaffolding — turning a convenience shortcut into a widespread credential exposure pattern.
- What is the blast radius if MCP config secrets leak?
- It depends on which secrets are exposed, but the worst case is substantial: Anthropic API keys, database passwords, cloud provider credentials, email service tokens, payment API keys, and OAuth client secrets all appear in real MCP config files. A leaked Anthropic key can generate unlimited model calls billed to your account. A leaked database password gives read/write access to production data. Secrets committed to a public repo are indexed by tools like GitHub search and credential scanners within minutes of the push.
- How do I scan for plaintext secrets in MCP config?
- Run `keelix scan` on the box. Keelix reads every MCP config path used by Claude Code, Cursor, Windsurf, and other agents it discovers on the host, flags any plaintext API keys or tokens it finds, and redacts them before the report is written — the actual secret never appears in output or telemetry. Each finding includes the config file path, the key type (inferred from format), the remediation step, and its SOC 2 / CIS control mapping.
- Does Keelix store or transmit the secrets it finds?
- No. Keelix runs entirely on your machine. When it discovers a plaintext secret, it redacts the value at the scan boundary — the report records that a finding exists, the config path, and the inferred credential type, but never the credential value itself. No data leaves your box. This is the same local-first, zero-telemetry design that applies to every Keelix scan.
- How do I fix plaintext secrets in MCP config?
- The safest approach for local MCP clients (Claude Code, Cursor) is to use the ${env:VAR_NAME} syntax in the config file to reference environment variables set in your shell profile, rather than hardcoding the value. For remote MCP servers, prefer OAuth 2.1 flows that remove token management from the client entirely. For secrets already committed to version control, rotate them immediately — assume they have been scanned and indexed — then scrub the history using a tool like git-filter-repo.
Related
Find out which secrets are in your MCP config right now.
Free, local-first, Apache-2.0. Secrets redacted at the boundary — nothing leaves your box.