你的编码 Agent 就是一座凭证库:Cursor、Claude Code、Copilot 与 MCP

·阅读约11分钟·Evergreen Tools Team

GitGuardian 在 2026 年 9 月发布的端点安全分析,把一件常被忽视的事讲清楚了:Cursor、Claude Code 与 GitHub Copilot 会把凭证留在开发机上——分布在配置文件、环境变量、日志、shell 历史与临时文件里。问题不在于这些工具「偷」了什么,而在于这些位置恰恰是仓库扫描与 CI 扫描从不查看的地方。支撑这条结论的证据来自 GitGuardian 的《State of Secrets Sprawl 2026》:在公开的 MCP 相关配置文件中发现了 24,008 个唯一密钥,其中 2,117 个是有效的;由 Claude Code 辅助的公开提交泄漏率为 3.2%,而全部公开 GitHub 提交的基线是 1.5%。

一、凭证是怎么留在机器上的:存储、复制、记录

先看凭证是怎么留在机器上的。GitGuardian 把它分成三类路径。被存储(Stored):API key 与 token 可能被放进 Agent 或 MCP 配置里,部分工具会改用操作系统钥匙串或 OAuth 流程。被复制(Copied):从 vault 或环境变量里取出的凭证,被写到本地文件上用于调试或复用,于是留下了一份无人管理的副本。被记录(Recorded):提示词、命令输出、调试日志与会话历史,在脱敏缺失或不完整时会把凭证一起记录进去。三类里,只有一部分可能被误提交进仓库,而很多根本不会进入任何仓库——这正是端点扫描与仓库扫描的分界线。

# 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.
端点上的凭证

扫描仓库看不到主目录里的凭证

二、各工具把状态放在哪

再看各工具把状态放在哪(路径以厂商 2026 年 9 月的文档为准,且经常变动)。Cursor 有两份 MCP 配置:项目内的 .cursor/mcp.json 会随仓库走,用户级的 ~/.cursor/mcp.json 永不进仓库;两者都接受内联凭证。Claude Code 的用户状态在 ~/.claude.json(含用户级 MCP 服务器与逐项目信任决策)与 ~/.claude/ 目录;登录 token 由钥匙串保护(macOS 用 Keychain,Linux 与 Windows 是受权限保护的文件,SSH 会话下 macOS 会回落该文件);项目内的 .mcp.json 被设计为可提交。Copilot CLI 的 MCP 配置在 ~/.copilot/mcp-config.json,同目录下还有 config.json(明文 token 回落)、logs/、session-state/、session-store.db、permissions-config.json 与 mcp-oauth-config/。两个工具都支持用环境变量(CLAUDE_CONFIG_DIR、COPILOT_HOME)搬走整个状态目录。

# 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.

三、证据:24,008 个密钥,以及 3.2% 对 1.5%

证据本身值得正视。GitGuardian 的《State of Secrets Sprawl 2026》在公开的 MCP 相关配置文件中找到 24,008 个唯一密钥,其中 2,117 个经验证仍然有效;同时发现由 Claude Code 辅助的公开提交里,密钥泄漏率为 3.2%,而全部公开 GitHub 提交的基线为 1.5%。这里有一个重要的限定:GitGuardian 明确表示该测量并未把因果关系归于工具本身,它说明的是——更快的开发并没有消除底层的凭证失误问题。对企业而言更值得注意的推论是:公开仓库暴露的只是可见的那一部分,而端点与内部仓库里可能存在同样的模式,只是不在公众视野里。

# 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.
配置文件与 shell 历史

凭证会「被存储、被复制、被记录」

四、MCP 配置如何变成凭证库

MCP 让这件事变严重了一个量级。Model Context Protocol 服务器让 Agent 能够访问源码管理、云厂商、数据库、SaaS 与内部系统,每一个新连接都可能引入一个新的非人类身份与一份具备真实组织访问权限的凭证。MCP 支持 OAuth、环境变量引用、凭证存储与运行时助手,但这些更安全的模式在无人管理的安装里并不会被自动强制执行。当凭证被内联写入时,上述三个工具的 MCP 配置文件就成了明文凭证库;而项目级文件还可能通过版本控制被共享——一次提交,就把 token 复制进了仓库历史,以及每一台拉到这份配置的机器。

# 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).

五、为什么现有控制看不见它

为什么现有控制看不见它?因为每种控制的边界都不覆盖这一层。仓库、pre-commit 与 CI 密钥扫描覆盖的是被跟踪内容、工作区改动或流水线输入,按各自配置生效,它们不会遍历无关的主目录配置、浏览器存储、shell 历史或临时文件。IdP、IAM 与 PAM 管理的是它们签发或观测到的身份与会话;本地复制的 API key 与无人管理的第三方 token 落在其边界之外。凭证管理器保护的是存储在 vault 中、通过 vault 取用的凭证,无法治理一份已经被写到日志、历史文件或临时文件里的副本——除非另有控制能发现它。

# 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))
密钥泄漏数据

公开 MCP 配置文件中 24,008 个唯一密钥

六、怎么补齐:端点侧的凭证发现

补齐方式的核心是「端点侧的凭证发现」。GitGuardian 的 Developer Endpoint Protection 给出四项能力:本地 Agent 与 MCP 清单(看清每台机器上跑了哪些 Agent 与 MCP 服务器、它们能访问什么)、机器扫描(按计划扫描配置、dotfile、日志、IDE 与 Agent 缓存、shell 历史、临时目录与浏览器存储)、AI hooks(通过受支持的 hook 做实时扫描,提示词提交与工具执行前检查可阻断,执行后检查只能通知)、以及蜜罐凭证(在每台受保护机器上放置一个不授予任何权限的诱饵 AWS 凭证,一旦被使用就按端点告警)。三个设计选择值得注意:检测在本地完成,GitGuardian 只通过 HasMySecretLeaked 协议收到 256 位 Scrypt 指纹与文件路径、时间戳等元数据,不接收明文密钥;部署推荐用 MDM 计划脚本,而非常驻的 EDR 式代理;它补充而非替代仓库扫描、vault、IdP、PAM、EDR 与 DLP。

📌 常见问题 FAQ

问题到底出在哪?

Cursor、Claude Code、GitHub Copilot 会把凭证留在主目录配置、环境变量、日志、shell 历史与临时文件里,而这些位置是仓库扫描与 CI 扫描从不覆盖的。

证据有多硬?

《State of Secrets Sprawl 2026》在公开 MCP 配置文件中发现 24,008 个唯一密钥、2,117 个有效;Claude Code 辅助的公开提交泄漏率为 3.2%,全部公开提交基线为 1.5%。

工具的主凭证安全吗?

部分工具把主登录 token 放在操作系统钥匙串;但 Claude Code 在钥匙串不可用(如 SSH)时会回落到文件,Copilot CLI 在无钥匙串时也会提示写入明文配置,Agent 能接触到的其它 API key 同样不受保护。

为什么 vault 解决不了?

vault 保护的是存进去、从 vault 取用的那份凭证,无法治理已经被写到日志、历史文件或临时文件的无人管理副本。

端点保护会不会把密钥传给厂商?

按 GitGuardian 的设计,检测在本地完成,只上传 256 位 Scrypt 指纹与文件路径、时间戳等元数据;可选的有效性校验由端点直接发往凭证所属的服务商,不经过 GitGuardian。