Claude Code 的“安全存储”名不副实:Linux 版 OAuth 令牌实际以明文保存

HN LLM Code Research 2026-09-07T06:09:05.981538

实测 Anthropic 的终端 AI 编程工具 Claude Code(2.1.257)发现,文档中承诺的“安全存储”MCP 认证令牌,在 Linux 上只是一个权限为 0600 的明文 JSON 文件,既未使用系统钥匙串,也未做内容加密。OAuth 协议解决的是委派与续期问题,不负责令牌的本地加密保存,这两件事不应混为一谈。

文档说的“安全存储”,落盘后是什么

Claude Code 是 Anthropic 推出的终端 AI 编程工具,支持通过 MCP(模型上下文协议,一种让 AI 模型安全调用外部工具和数据的标准接口)接入外部服务。在官方 MCP 文档中,Claude Code 被描述为会“安全存储”认证令牌。

但实际检查后会发现,当用户完成多个远程 MCP 服务器的认证后,令牌并没有进入某个加密保险库,而是被写入了 ~/.claude/.credentials.json 文件,权限为 0600。

0600 表示只有当前系统用户能读写这个文件,从账号隔离角度看没有明显问题。可文件内容本身是明文 JSON,顶层的 mcpOAuth 对象直接存放了 OAuth 访问令牌。以下是审计中看到的某服务条目结构(凭据值已打码):

{
  "mcpOAuth": {
    "cloudflare": {
      "accessToken": "...",
      "refreshToken": "...",
      "expiresAt": "..."
    }
  }
}

Anthropic 的凭据管理文档对各个操作系统的说明如下:

也就是说,文档里“安全存储”的实际覆盖范围,比一般人看到这四个字时理解的要窄得多。在 Linux 上,所谓“安全”既不代表加密,也不代表专门的密钥保管机制,而只是一层文件系统权限。

“安全存储”不等于“加密存储”

文档与实际落盘方式之间存在落差,但这本身未必是一个可被直接利用的漏洞。更值得讨论的是,这种说法可能给用户带来不切实际的安全预期。

如果 Claude Code 只是跑在一台个人 Linux 笔记本上,0600 权限通常已经能阻止其他本地用户读取。可一旦这台设备涉及备份同步、日志采集、崩溃报告打包,或曾经被其他人临时使用,那么明文 JSON 被读走的风险就会上升。开发者需要清楚这一现实,而不是被“安全存储”四个字误导。

OAuth 解决委派问题,而不是存储加密

实际使用 Claude Code 连接远程 MCP 服务器时,授权流程通常走浏览器 OAuth:

  1. 在终端执行 claude mcp login
  2. 在弹出的浏览器页面点击“批准”
  3. 回到终端,MCP 服务器已连接

整个过程没有手动复制粘贴令牌的操作,所以很容易忽略背后的一个事实:Claude Code 确实拿到了一份凭据。只要连接需要在重启后依然有效,这份凭据就必须落盘保存。

OAuth 本身很有价值。它把授权服务器发现、作用域请求、授权码交换、访问令牌刷新和撤销等流程标准化。Anthropic 的 MCP 文档甚至让用户能限制 Claude Code 可请求的作用域。

但关键在于:OAuth 协议并不规定“本地保险库如何实现”。它管的是“令牌怎么发、怎么续期”,而不是“令牌怎么保存”。

API 令牌同样可以设置作用域、限定资源、设置过期时间、轮换甚至独立撤销。OAuth 真正带来的增量好处,是把“委派与续期”标准化,同时省去了手动复制粘贴令牌。可这些改进不会让最终落盘的令牌因为你走的是 OAuth 流程,就自动获得更强的存储安全。

简单区分:

这是两个互相独立的问题,不应该混淆。

持久化层应当支持替换

目前 Claude Code 默认把所有 Linux 用户的 MCP 令牌写入同一个明文文件,这个设计并不理想。更好的做法是把凭据持久化层做成可替换的接口,让用户或组织依据自己的安全基线,选择系统钥匙串、密码管理器、加密文件,或是云端密钥管理服务作为后端。

要实现这一点并不需要从零设计新标准。SecretSpec 就是一个现成的开源密钥解析规范,提供 Node.js / TypeScript SDK,并内置了 Rust 解析器——这意味着开发者可以基于它快速构建适配不同存储后端的接入层。

关键要点

更安全的备选存储方案

{
  "keyStore": {
    "provider": "system keyring"
  }
}

provider 设成 "system keyring" 只是其中一个选项。团队也可以改用 OpenBao 或云厂商的 KMS 来集中管理密钥;对于没有桌面环境的 Linux 工作站,还能用 age 加密文件作为后端。无论落到哪种方案,OAuth 令牌的获取和续期流程都不受影响——变的只是凭据存放的位置,从默认的明文文件挪到了受保护的存储里。

这并非纸上谈兵。OpenAI Codex 已经走出了第一步:它把 MCP 的 OAuth 存储做成了可配置项,支持 autofilekeyring 三个选项。用户只要把 mcp_oauth_credentials_store 设成 "keyring",就能启用系统钥匙串。这套做法离通用的密钥 provider 接口还有距离,但至少 Linux 用户不再只有“明文文件”一条路可走。

SecretSpec 协议本身也在持续迭代。0.21 版本正在开发带版本管理的解析器,以及 provider 之间的进程间通信(IPC),目标是做到零依赖集成——应用可以直接通过本地协议调用 SecretSpec 的 provider,不再需要往自己的项目里塞进 SDK 或 provider 实现。

理想状态:直接读取,而不是再造一份

SecretSpec 的长期目标可以概括成一句话:让开源工具直接从用户已有的密钥存储中读取凭据,而不是在旁边再复制出一份新的明文凭据文件。这项工作已经在多个方向落地:

查看原文