Your Coding Agent Is a Credential Store: Cursor, Claude Code, Copilot and MCP
GitGuardian's September 2026 endpoint security analysis makes a frequently ignored point explicit: Cursor, Claude Code and GitHub Copilot leave credentials on the developer's machine, scattered across config files, environment variables, logs, shell history and temporary files. The problem is not that these tools take anything. It is that those locations are exactly the ones repository and CI scanners never look at. The supporting evidence comes from GitGuardian's State of Secrets Sprawl 2026, which found 24,008 unique secrets in public MCP-related configuration files, 2,117 of them valid, along with a 3.2 percent leak rate in Claude Code-assisted commits against a 1.5 percent baseline across all public GitHub commits.
1. How a Credential Comes to Stay: Stored, Copied, Recorded
Start with how a credential comes to stay on the machine. GitGuardian separates three paths. Stored: API keys and tokens can be placed in agent or MCP configuration, and some tools use the OS keychain or an OAuth flow instead. Copied: a credential retrieved from a vault or environment can be written to a local file for debugging or reuse, leaving an unmanaged copy. Recorded: prompts, command output, debug logs and session history can capture a credential when redaction is absent or incomplete. Only some of these artifacts can be committed by accident, and many never enter a repository at all. That boundary is precisely where repository scanning ends and endpoint reality begins.
# The scanner you already run covers tracked content. The credentials
# live elsewhere: home-directory config, dotfiles, logs, caches, shell
# history, temp files and browser storage on the developer's machine.
PATHS = [
"~/.cursor/mcp.json", # Cursor, user-level MCP (never committed)
"**/.cursor/mcp.json", # Cursor, project-level MCP (committed!)
"~/.claude.json", # Claude Code, user-level state
"~/.claude/.credentials.json", # Claude Code, login-token fallback
"**/.mcp.json", # Claude Code, project MCP (shared)
"~/.copilot/mcp-config.json", # Copilot CLI, MCP servers
"~/.copilot/config.json", # Copilot CLI, plaintext token fallback
"~/.copilot/logs/", "~/.copilot/session-state/",
"~/.copilot/session-store.db", "~/.copilot/permissions-config.json",
"~/.copilot/mcp-oauth-config/",
"~/.zsh_history", "~/.bash_history",
]
# Note the asymmetry: the user-level file sits outside version control
# entirely, while the project-level file is designed to be committed and
# pulled onto every machine that clones the repository.Repository scanning never sees home-directory credentials
2. Where Each Tool Keeps Its State
Now look at where each tool keeps state, with paths as documented by the vendors in September 2026; they change often. Cursor keeps two MCP configurations: a project file at .cursor/mcp.json that travels with the repository, and a user file at ~/.cursor/mcp.json that never does. Both accept inline credentials. Claude Code keeps user state in a home directory plus ~/.claude.json, which holds the sign-in session, user-level MCP servers and per-project trust decisions. Its login token is the best-protected item: the OS keychain on macOS, a permission-protected file on Linux and Windows, with macOS falling back to that file when the keychain is unavailable, such as in an SSH session. Its project-scoped .mcp.json is designed to be checked into version control. Copilot CLI stores MCP servers in ~/.copilot/mcp-config.json, and the same directory holds config.json with a plaintext token fallback, logs, session state, a SQLite session store, saved permission decisions and fallback storage for MCP OAuth material. Both tools let a developer relocate the whole state directory with an environment variable.
# The strongest control is the one that runs before the action, not after.
# "AI hooks" scan the prompt, the command, the file read and the MCP call.
# Pre-tool checks can block; post-tool checks can only notify, because the
# tool has already executed by then.
def pre_tool_hook(event):
blob = "\n".join(str(v) for v in event.values())
for secret in scan_for_secrets(blob): # same rules as ggshield
return {
"decision": "block",
"reason": f"secret in {event['kind']}: {secret.rule_id}",
"location": secret.file_path,
}
return {"decision": "allow"}
# Hook kinds worth covering: prompt submission, command execution,
# file read, and every outbound MCP tool call. A prompt is an egress path.3. The Evidence: 24,008 Secrets, and 3.2 Percent Against 1.5
The evidence deserves attention. State of Secrets Sprawl 2026 found 24,008 unique secrets in public MCP-related configuration files, of which 2,117 were valid, and measured a 3.2 percent secret-leak rate in public commits assisted by Claude Code against a 1.5 percent baseline across all public GitHub commits. One important limitation: GitGuardian states that this measurement does not assign causation to the tool itself. What it shows is that faster development has not removed the underlying credential failure problem. The inference that matters for enterprises is that public repositories expose only the visible portion; endpoints and internal repositories can hold the same pattern outside public view.
# Honeytokens change the economics: you stop searching for leaks and start
# waiting for use. The decoy grants no access, and any attempt to use it
# raises an alert tied to a specific endpoint.
honeytoken:
provider: aws
kind: decoy_access_key
grants_access: false
planted_on: all_protected_endpoints
on_use:
alert: "honeytoken_used"
attributes: [endpoint_id, user, process_tree, timestamp]
rotation: monthly
# Because it never grants real permissions, a false positive is cheap for
# you and the signal is high-quality: nothing legitimate should ever use it.Credentials get stored, copied and recorded
4. How MCP Config Becomes a Credential Store
MCP makes this an order of magnitude worse. Model Context Protocol servers let agents reach source control, cloud providers, databases, SaaS applications and internal services, and every new connection can introduce another non-human identity and another credential with real organisational access. MCP supports OAuth, environment-variable references, credential stores and runtime helpers, but those safer patterns are not automatically enforced across unmanaged installations. When credentials are written inline, the MCP configuration files of these tools become plaintext credential stores. Project-scoped files can also be shared through version control, which means one commit distributes a token into repository history and onto every machine that receives the configuration.
# Why repository and CI scanning cannot cover this, stated as two sets.
# If your control's scope is set A and the credential lives in set B, the
# control is working correctly and the leak still happens.
scanned_by_repo_ci = {
"tracked files in git",
"working-tree changes (per config)",
"pipeline inputs",
}
never_scanned_by_repo_ci = {
"home-directory config (~/.cursor, ~/.claude, ~/.copilot)",
"logs and session histories",
"shell history",
"temporary files",
"browser storage",
"IDE and agent caches",
}
missing = never_scanned_by_repo_ci - scanned_by_repo_ci
# Same logic defeats IdP/IAM/PAM (session-scoped) and vaults (they protect
# the copy inside the vault, not the unmanaged copy written to disk).5. Why Your Existing Controls Miss It
Why do existing controls miss it? Because each control's perimeter excludes this layer. Repository, pre-commit and CI secret scanning covers tracked content, working-tree changes or pipeline inputs, according to configuration; it does not traverse unrelated home-directory config, browser stores, shell history or temporary files. Identity providers, IAM and privileged access management govern the identities and sessions they issue or observe; locally copied API keys and unmanaged third-party tokens fall outside that perimeter. Secrets managers protect credentials stored in and retrieved through the vault, and cannot govern an unmanaged copy already written to a log, history file or temporary file unless another control discovers it.
# Remediation is a workflow, not a scan result. The order below matters:
# rank first, revoke second, then relocate and redact. Revoking before you
# have ranked can take an environment down for a secret that was already
# expired.
FINDING = {"secret_id": "aws-ak-...", "severity": "critical",
"endpoint": "laptop-117", "access_scope": "prod-s3-write",
"valid": True, "file": "~/.cursor/mcp.json"}
def remediate(f, has_vault, has_owner):
steps = []
steps.append(("rank", f"severity={f['severity']}, scope={f['access_scope']}"))
if f["valid"]:
steps.append(("revoke", "rotate the credential at the provider now"))
if has_vault:
steps.append(("relocate", "replace the inline value with a vault reference"))
steps.append(("redact", "remove the string from the local file and its logs"))
if not has_owner:
steps.append(("assign", "name an owner or the finding will resurface"))
return steps
print(remediate(FINDING, has_vault=True, has_owner=True))24,008 unique secrets found in public MCP configs
6. The Fix: Endpoint-Side Credential Discovery
The fix is fleet-wide credential discovery on the device. GitGuardian's Developer Endpoint Protection offers four capabilities: a local agent and MCP inventory that shows which supported agents and MCP servers run on each endpoint and what they can reach; a machine scan that covers config files, dotfiles, logs, IDE and agent caches, shell history, temporary directories and browser storage on a schedule; AI hooks that scan in real time through supported coding-tool hooks, where prompt submission and pre-tool checks can block and post-tool checks can only notify because the tool has already run; and honeytoken protection, which plants a decoy AWS credential that grants no access and raises an alert tied to the endpoint if anyone tries to use it. Three design choices matter. Detection is local, with GitGuardian receiving a 256-bit Scrypt fingerprint through the HasMySecretLeaked protocol plus metadata like file path and timestamp, never the plaintext secret. Deployment is recommended as an MDM-scheduled script rather than a resident EDR-style agent. And it complements repository scanning, vaults, IdP, PAM, EDR and DLP rather than replacing them.
📌 Frequently Asked Questions
What exactly is the problem?
Cursor, Claude Code and GitHub Copilot leave credentials in home-directory config, environment variables, logs, shell history and temporary files, none of which repository or CI scanning inspects.
How strong is the evidence?
State of Secrets Sprawl 2026 found 24,008 unique secrets in public MCP configuration files, 2,117 of them valid, and a 3.2 percent leak rate in Claude Code-assisted commits against a 1.5 percent baseline across all public GitHub commits.
Is the tool's primary credential safe?
Some tools protect the main login token in the OS keychain, but Claude Code falls back to a file when the keychain is unavailable, such as over SSH, and Copilot CLI prompts for plaintext config storage when no keychain exists. Other API keys the agent encounters are not protected at all.
Why does a vault not solve this?
A vault protects the credential stored in and retrieved through it. It cannot govern an unmanaged copy already written to a log, history file or temporary file.
Does endpoint protection send secrets to the vendor?
By design, detection happens locally. GitGuardian receives a 256-bit Scrypt fingerprint plus metadata such as file path and timestamp. Optional validity checks send the credential directly from the endpoint to its provider, not through GitGuardian.
🔧 Recommended Tools
📚 Sources
- GitGuardian — AI Coding Agents Are Leaking Credentials: Cursor, Claude Code, Copilot, and MCP
- GitGuardian — The State of Secrets Sprawl 2026 (24,008 secrets in public MCP configs; 3.2% vs 1.5%)
- Cursor — Model Context Protocol (MCP) documentation
- Claude Code — MCP and credential management documentation
- GitHub — Copilot CLI authentication and config directory reference