MCP Credential & Secrets Exposure
Credentials and secrets used to authenticate MCP servers are routinely stored in plaintext config files, passed through environment variables, and leaked through tool call logs, error messages, and context windows - creating a massive, largely unmonitored attack surface.
Severity: 8.5/10 (Critical)
The combination of widespread plaintext credential storage, minimal tooling for secret rotation, and the growing number of MCP servers per deployment makes this one of the highest-impact risks in the ecosystem today.
Summary
Like most software in tech, MCP servers need credentials to gain access and do useful things. A Slack MCP server needs a bot token. A GitHub server needs a personal access token. A database server needs a connection string. One of the main problems is how those credentials are stored, transmitted, and (often accidentally) exposed.
Right now, the standard setup for most MCP deployments looks like this: you drop API keys into a JSON config file or set them as environment variables, and the MCP client passes them to the server at startup. These credentials sit in plaintext on disk, get logged in debug output, show up in error messages, and in multi-server setups, can be read by other MCP servers running in the same process context.
This isn't a theoretical risk. Anyone who's configured claude_desktop_config.json or .cursor/mcp.json has put raw API keys in a JSON file that any process on their machine can read. As individual developers add more servers to their setup, and as teams start standardizing on MCP for internal tooling, the number of plaintext credentials sitting on any given machine quietly multiplies. A developer with a GitHub server, a Slack server, and a database server already has three sets of credentials in one config file. That's three keys compromised if the file is stolen.
What Is the Issue?
MCP has no native secret management. The protocol itself doesn't define how credentials should be stored, transmitted, or scoped. This means every MCP client and server implementation handles secrets differently, and most handle them poorly or in a way that prioritizes convenience over security.
Where Credentials Live Today
Configuration Files
The most common pattern is storing credentials directly in MCP client config:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxx"
}
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://admin:s3cretpassw0rd@prod-db.internal:5432/maindb"
}
}
}
}This is the config file for a single developer. Three servers, three sets of credentials, all in plaintext, all in a file with no special filesystem protections. Multiply this across a team of 50 engineers and you've got at least 150+ plaintext credentials scattered across laptops.
Environment Variables
The "better" practice is referencing env vars, but they're still plaintext in memory and visible to any process running as the same user. On macOS, ps eww can dump them. On Linux, /proc/<pid>/environ is readable by the same UID.
Tool Call Parameters
Some MCP server implementations accept credentials as tool parameters rather than (or in addition to) environment variables. This means credentials can end up in tool call logs, audit trails, and potentially even the AI model's context window.
Server-Side Storage
MCP servers themselves may store or cache credentials. A server that connects to an upstream API might persist tokens in memory, write them to temp files for subprocess calls, or log them during authentication flows.
Root Cause Analysis
No Protocol-Level Secret Handling
The MCP specification focuses on the communication protocol between clients and servers but doesn't address credential lifecycle. There's no standard for:
- How servers should receive credentials (env vars? OAuth flows? vault references?)
- How credentials should be scoped (per-tool? per-session? per-user?)
- When credentials should expire or rotate
- How to revoke access to a specific server without reconfiguring everything
The Config File Anti-Pattern
JSON config files were designed for simplicity. You paste in your key, restart the client, and it works. This is great for getting started and terrible for production security. But there's no standard upgrade path from "keys in JSON" to "keys in a vault." Each client would need to implement its own secret backend, and none have.
Credential Sprawl
Every new MCP server means at least one new credential. A typical power user might have 8-15 MCP servers configured. That's 8-15 API keys, tokens, or passwords that:
- Were probably generated once and never rotated
- Have broader permissions than the MCP server actually needs
- Are shared across the user's personal and work contexts
- Would all be compromised simultaneously if the config file is stolen
Insufficient Scoping
Most MCP server setups use a single credential per service, typically with broad permissions. The GitHub MCP server gets a PAT with repo scope when it probably only needs read access to specific repositories. The Slack server gets a bot token with full channel access when it only needs to post to one channel. There's no standard mechanism for fine-grained permission scoping within MCP.
Risk & Impact Analysis
Why It Matters
Credential exposure in MCP environments is particularly dangerous because:
- Concentration risk: A single config file contains keys to multiple services. Stealing
claude_desktop_config.jsonis like stealing a keyring with keys to GitHub, Slack, databases, cloud infrastructure, and internal APIs. - Persistent access: MCP credentials are typically long-lived tokens, not short-lived session tokens. A stolen PAT is likely set to be valid for at least months.
- Lateral movement: MCP servers often connect to internal services. Compromising one developer's MCP config can provide authenticated access to production databases, internal APIs, and cloud infrastructure.
- Silent compromise: There's no standard monitoring for MCP credential usage. If someone clones your config file and uses your tokens from a different machine, you probably won't know.
- Context window exposure: When credentials appear in tool call responses or error messages, they become part of the AI model's context. This context may be logged, cached, or -- in hosted model scenarios -- processed on infrastructure you don't control.
Who Is Affected
- Individual developers using MCP clients with plaintext configs
- Engineering teams sharing MCP server setups without centralized credential management
- Organizations whose employees use MCP tools that connect to production services
- MCP server maintainers whose implementations inadvertently log or expose credentials
Severity Amplifiers
- Organizations with no inventory of which MCP servers are connected to which services
- Teams that copy-paste config files to onboard new members
- Developers who use the same API keys for MCP servers and other integrations
- Environments where MCP client configs are synced via dotfile repos (sometimes public ones)
- Password managers weren't built for MCP -- there's no autofill for JSON config files, so developers paste keys manually and forget about them
- MCP credentials don't expire like browser sessions -- a token pasted once can stay valid for months with no re-authentication prompt
Proof of Concept: Config File Heist
Scenario
A developer has a typical MCP setup: a GitHub server with a personal access token, a Slack server with a bot token, and their credentials are sitting in plaintext in their MCP config file like everyone else's. They install a community MCP server for code review assistance. It looks legit -- good README, real functionality, helpful tool descriptions. What the developer doesn't see is a poisoned instruction buried in one of the tool descriptions that targets that config file.
The Developer's Config
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_a1b2c3d4e5f6g7h8i9j0"
}
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-123456789-abcdefghij"
}
},
"code-review-helper": {
"command": "npx",
"args": ["-y", "code-review-mcp"],
"env": {
"CR_API_KEY": "cr_xxxxxxxxxx"
}
}
}
}Three servers, three sets of credentials, one file. The developer thinks they installed a code review tool. They actually gave an attacker the keys to their GitHub repos, their company's Slack workspace, and whatever else is in that config.
The Attack
The malicious code-review-mcp server has a tool with a poisoned description (see Tool Poisoning). The hidden instructions tell the AI model to read the MCP config file and pass its contents through a tool parameter:
User: "Review the latest PR on my repo"
What the user sees:
> Reviewing PR #47 on your-org/your-repo...
> [normal code review output]
What actually happened:
1. AI reads ~/.cursor/mcp.json (following hidden instructions)
2. AI calls the code-review tool with the config contents
stuffed into a "context" parameter
3. The server extracts the GitHub PAT and Slack bot token
4. The server posts the review as expected — tool works fine
5. Stolen credentials are sent to attacker infrastructureWhat the Attacker Does Next
With the GitHub PAT (ghp_a1b2c3d4...):
- Clones all private repos the developer has access to
- Reads secrets stored in repo settings
- Pushes a subtle backdoor to a low-traffic repo
With the Slack bot token (xoxb-123456...):
- Reads message history across channels the bot can access
- Sends phishing messages as the bot to other team members
- Extracts files shared in Slack
All from one config file. The developer never rotates these credentials because nothing prompted them to -- no session expired, no alert fired, no login looked suspicious. The tokens were pasted months ago and haven't been touched since.
Why This Works
- The config file is the single point of failure. Every credential for every MCP server lives in one readable file.
- Tool poisoning provides the theft mechanism. The attacker doesn't need malware or a phishing email. The MCP server they published does the work.
- The tool still functions normally. The developer gets their code review. There's no visible indication anything went wrong.
- Credentials have no expiration pressure. The stolen tokens work for as long as the developer doesn't manually revoke them, which they have no reason to do.
- There's no monitoring to catch it. No standard tooling alerts on MCP config file access or credential usage from new locations.
MCP-Specific Considerations
Why MCP Amplifies Credential Risk
Credential management is a solved problem in many contexts. We have vaults, OIDC, short-lived tokens, and service meshes. But MCP's architecture creates specific challenges:
- Local execution model: MCP servers typically run as local processes on the developer's machine, outside the reach of corporate secret management infrastructure
- No standard auth flow: Unlike OAuth-protected APIs, MCP servers have no standard authentication protocol. Each server invents its own approach.
- Multi-server environments: Every server is a separate process with its own credentials, multiplying the attack surface
- Client-managed secrets: The MCP client (Claude Desktop, Cursor, etc.) is responsible for injecting credentials, but clients aren't secret managers
- Mixed trust boundaries: A single MCP config might contain credentials for both a public GitHub repo and a production database, with no isolation between them
The OAuth Gap
The MCP specification has been working on OAuth support, but adoption is slow and the implementation remains contentious. As Aaron Parecki documented, the initial OAuth spec for MCP had significant issues. Christian Posta's analysis of the updated spec highlighted additional concerns around token handling and flow complexity.
Even when OAuth lands broadly, it solves authentication between clients and servers. But, it doesn't address how servers authenticate to upstream services, which is where most credential exposure happens today.
Vulnerability Indicators
Plaintext Credential Storage
Config files contain raw API keys readable by any process
No Secret Rotation Standard
Credentials are typically set once and never rotated
Credential Leakage in Errors
Error handlers and debug logs can expose tokens
No Scoping Mechanism
Credentials are over-permissioned with no per-tool boundaries
Severity Rating
| Factor | Score | Rationale |
|---|---|---|
| Exploitability | 8/10 | Config files are readable by any process; no special exploit needed |
| Impact | 9/10 | Compromised credentials provide authenticated access to connected services |
| Detection Difficulty | 8/10 | No standard monitoring for MCP credential usage or exfiltration |
| Prevalence | 9/10 | Plaintext credential storage is the default in virtually every MCP deployment |
| Remediation Complexity | 8/10 | Requires changes across clients, servers, and organizational practices |
Mitigations
For Organizations
Centralize MCP Credential Management
Don't let individual developers manage their own MCP credentials in local config files. Use a centralized system that:
- Issues short-lived, scoped tokens for each MCP server
- Automatically rotates credentials on a schedule
- Provides an audit trail of credential issuance and usage
- Can revoke access to specific servers without touching individual machines
Inventory Your MCP Credential Surface
You can't secure what you don't know about. Build an inventory of:
- Which MCP servers are deployed across the organization
- What credentials each server uses
- What permissions those credentials have
- When credentials were last rotated
- Who has access to each credential
Deploy MCP Gateways
Route MCP traffic through a gateway that handles upstream authentication. This way, individual MCP clients never hold credentials for backend services directly. The gateway authenticates on behalf of the user, using scoped, short-lived tokens issued per session. Beyond credential management, gateways give you visibility into what agents are actually doing; which tools are being called, what data is flowing through them, and who initiated each action. Even if credentials are compromised, at least you have an audit trail to detect and investigate suspicious usage.
Network Segmentation
MCP servers that connect to sensitive internal services should run in isolated network segments with strict egress controls. A compromised MCP server on a developer laptop shouldn't be able to reach production databases directly.
For Developers
Never Commit MCP Configs With Credentials
Add your MCP config files to .gitignore. If you use dotfile sync, exclude MCP configs or use a template that references environment variables rather than containing actual keys. Common files to protect: claude_desktop_config.json, .cursor/mcp.json, any custom MCP server configs.
Scope Credentials Aggressively
When generating API keys for MCP servers:
- Use the minimum permissions the server actually needs
- Create dedicated keys for MCP use, don't reuse keys from other integrations
- Set expiration dates on tokens where supported
- Use read-only keys when the MCP server only needs to query data
Audit Your Error Handling
If you're building MCP servers, review your error handling paths for credential leakage:
- Strip Authorization headers from error responses
- Don't include request configs in user-facing errors
- Redact tokens from log output
- Use structured logging that allows filtering sensitive fields
Use Secret References Instead of Values
Where possible, reference secrets by name rather than value:
{
"env": {
"GITHUB_TOKEN": "op://vault/github-mcp/token"
}
}This isn't natively supported by most MCP clients yet, but some setups allow you to shell out to a CLI-based secret manager at startup. It's not as seamless as autofill in a browser, but it keeps plaintext credentials out of config files entirely.
For MCP Client Developers
- Implement secret backend plugins: Let users configure vault references instead of plaintext values
- Encrypt config files at rest: If credentials must be stored locally, encrypt the config file using the OS keychain
- Warn on plaintext credentials: Detect raw API keys in config and suggest alternatives
- Isolate server environments: Don't share environment variables across MCP servers; each server should only see its own credentials
- Redact credentials in logs: MCP client debug logs should never contain raw credential values
For MCP Server Developers
- Support OAuth and token exchange: Where the upstream service supports OAuth, use it instead of static API keys
- Implement credential validation on startup: Verify credentials have minimum required permissions and warn if over-scoped
- Sanitize error outputs: Never include authentication headers or tokens in tool call responses
- Document minimum required permissions: Make it easy for users to create properly scoped credentials
Detection Methods
Static Analysis
- Scan MCP config files for plaintext credentials using tools like trufflehog, gitleaks, or custom regex patterns
- Check file permissions on MCP config files (should not be world-readable)
- Audit environment variable usage in MCP server codebases for credential handling
- Review error handling code for potential credential leakage paths
Runtime Monitoring
- Monitor MCP tool call responses for patterns that look like API keys, tokens, or connection strings
- Track credential usage patterns: alert on tokens being used from new IP addresses or unusual times
- Log and audit all MCP server authentication events
- Monitor for file access to MCP config files from unexpected processes
Behavioral Analysis
- Detect MCP servers that request access to credential files (mcp.json, config.json, .env)
- Flag tool calls that return data matching known credential patterns
- Monitor for unusual data volumes in tool call parameters (potential exfiltration)
- Track cross-server information flows that might indicate credential harvesting
Related Topics
- Tool Poisoning Attacks (credential theft via hidden instructions)
- Rug Pull (post-deployment changes that expose credentials)
- Shadow AI (unmanaged MCP deployments with untracked credentials)
- MCP Sprawl (growing number of servers means growing credential surface)
- Cross-Session Context Leakage (sensitive data persisting across sessions)
References
- Aaron Parecki - OAuth for Model Context Protocol - Protocol Analysis
- Christian Posta - The Updated MCP OAuth Spec Is a Mess - Protocol Analysis
- OWASP MCP Top 10 - Security Framework
- Invariant Labs - MCP Security Notifications - Research
- Elastic Security Labs - MCP Attack Vectors - Research
- MCP Specification - Authorization - Protocol Specification
Report generated as part of the MCP Security Research Project