跳到正文
ChatGPT Pro 俱乐部
博客

Claude Code 凭据泄露:点一次「不要再问」,1,122 份凭据进了公开仓库

21 阅读约 15 分钟读完

11 月 5 日,安全公司 Apiiro 发了一份关于 Claude Code 凭据泄露的研究:GitHub 上 104,185 份 .claude/settings.local.json 里,1,122 份含有真实凭据——数据库密码、云厂商 API key、SSH 服务器 root 密码,甚至一家数据库厂商自己的管理级云密钥。把历史提交、团队共享的 settings.json 和 agent 会话记录都算进去,总量是 2,539 个真实凭据,散落在 1,994 个公开仓库。

这条线索没有停在研究报告上。帖子在 Hacker News 上拿到 500 分、358 条评论,Anthropic 的 Claude Code 负责人 Boris Cherny 在 11 月 7 日进场回应,周末连发了十条。官方第一次公开承认:Claude Code 历史上从来没做过凭据清洗(secret scrubbing)。

说明一下与前一篇的关系:站内 1148 那篇讲的是 Claude Code 把账号邮箱写进 User-Agent 发给第三方网站,是外发路径的问题。这一篇完全不同——是留存在本地、再被自己人提交进仓库的问题,而且这个文件本来就是 Claude Code 自己写的。

一句话结论

  • 这不是漏洞,是设计。你点「是,并且不要再问」,Claude Code 就把整条命令原文存成一条 allow 规则写进项目目录里的 .claude/settings.local.json。命令里带密码,规则里就带密码。
  • 防护走错了通道。Claude Code 确实尝试把文件挡在提交之外,但它加的是你的全局 gitignore,不是仓库自己的 .gitignore。换个机器、换个容器、复制一个文件夹、或者文件已经被跟踪,这层防护就失效。
  • 通用扫描器救不了你。gitleaks 和 TruffleHog 各扫一遍 104,185 份文件,有 215 份两边都没报。漏掉的恰恰是 PGPASSWORD=、sshpass -p 后面那种样子普通的密码。
  • 官方的回应把责任外推。Cherny 的建议是「大家都该用 auto 模式」「你们都该开 GitHub 的 secret scanning」。报告作者和评论区指出:auto 模式遇到敏感命令同样会问,而默认选项是「始终允许」,同一个坑还在。
  • 最实用的一条结论:别把密码打进命令里,用 $VAR。规则记录的是 $DB_URL 而不是那个密码,这个坑就从结构上消失了。

1)一条命令,怎么变成一次公开泄露

机制很简单,而且每一步都很合理。

开发机上本来就有凭据:配置文件里的数据库连接串、本地装的云厂商 key、.env 里的 API token。你让 Claude Code 去看「staging 为什么登录失败」或者「跑一下这个 migration」,它自然会提出一条带凭据的命令——不带就跑不通。Claude Code 会先问你要不要执行,一个会话里要问很多次。问到第十次,基本没人再逐字读命令行,而一个密码在一条长命令里只占一个词的位置。

你选了「是,并且不要再问」。Claude Code 于是做两件事:执行这条命令,并把这条命令本身作为一条 allow 规则,追加到项目文件夹里的 .claude/settings.local.json。

Apiiro 特别写明:这是 Claude Code 程序本身的行为,不是模型的行为。他们用 Opus 5.5 和 Fable 5.1 都复现了。也就是说,换个更强的模型不会好一点。

深色桌面上散落的多把金属钥匙,其中一把压在一张纸上
凭据被记进批准规则里,就像钥匙被抄在一张会被随手传出去的纸上。

问题的关键在最后一环。settings.local.json 本该只留在开发机上,Claude Code 也确实想把它留住——它在创建这个文件时,会把一条匹配 .claude/settings.local.json 的规则写进你的全局 git 排除文件(core.excludesFile,或 ~/.config/git/ignore)。这覆盖了常见情况,但漏掉四种:文件夹之后才变成仓库、换一台机器克隆、文件已经被提交过、以及有人手工建了这个文件。

而且仓库自己的 .gitignore 从头到尾没有被改过。所以「保护」这件事从来没有跟着项目一起走——换台机器 clone 下来,它就是一堆普通文件里的一个普通文件。

更麻烦的是 Claude 会自己提交它。在 Apiiro 追踪到有真实凭据的 1,105 个仓库里,539 个存在一个由 Claude 共同署名(co-authored)的提交改动了这个文件,署名覆盖从 Sonnet 4.5 到 Opus 5.5 的各代模型。Claude Code 的提交指引明确叫模型不要提交 .env 和 credentials.json——唯独没把自己写的这个设置文件写进名单。

2)Claude Code 凭据泄露的规模有多难看

先把统计口径说清,因为这里的数字很容易被误读。

统计项数量
公开 GitHub 上的 settings.local.json 副本104,185 份,分布在 103,617 个仓库
其中含真实凭据的1,122 份(1.1%)
这 1,122 份里的不同凭据1,379 个(636 个厂商格式 key + 743 个密码/token)
历史提交、共享 settings.json、会话记录合计2,539 个凭据,1,994 个仓库
已删除、但仍在历史里的凭据734 个,涉及 509 个仓库
被 fork 带走的74 个仓库的 211 个 fork
数据来自 Apiiro 对 GitHub 公开代码与提交的只读检索,2026-11-01 至 11-03。所有文件在 2026-11-02 都还在默认分支上。

「真实凭据」的口径需要说明:匹配已知厂商 key 格式,或者一个密码/token 紧邻一个公网主机。Apiiro 明确表示没有使用或测试任何一个凭据,也没有联系任何主机——所以这不是「确认有效」的统计,是「看起来能用的」统计。起作用的过滤规则去掉了占位符、常见默认密码、本地和私有主机、以及设计上就公开的 key(比如 Supabase anon key、Firebase web config),最后剩下的 2,774 个值逐一交给独立 LLM 评审复核。

另外两个数字值得单独看。

一是「文件越大越危险」。超过 8 KB 的 allowlist 里,每十份就有一份含真实凭据;500 字节以下的,不到五百分之一。这条曲线基本就是「你点了多少次『不要再问』」的曲线——用得越久,攒得越多。

二是「躺了很久没人发现」。在有真实凭据的仓库里,这个文件首次被提交的时间,中位数是 2026-11-02 之前的 217 天;有 118 个仓库超过一年。这不是上周刚出事,是一个长期没人看的角落。

顺带说一句:Apiiro 对比了 16 款编程 agent 的文档,只有 Claude Code 被确认会在默认行为下把整条被批准的命令写进项目文件。这句话的分量比「1,122」这个数字更大,因为它指向的是设计选择。

3)被曝的是谁

63 个组织持有真实凭据。报告里点了类型,但没点名字(他们按承诺在 11 月 2 日和 3 日逐个通知了这些组织)。

类型曝出来的东西
一家数据库厂商自己的云 API key,带 manage 权限、无过期声明,外加集群主机
一家 B2B 创业公司生产环境 Azure Postgres 密码
一家软件代理公司Square 生产环境 access token
一个千星以上开源项目OpenAI 格式 API key
一家 IT 服务商(医疗记录应用)该应用云 Postgres 数据库的 owner 密码
一家支付创业公司postgres 角色的 Postgres 密码
一个大学仓库对一台公网服务器的 root SSH 永久授权
每一例都在固定提交上核过。名单从学生社团、黑客松小组,一直到创业公司、软件代理和 IT 服务商。

凭据类型分布也值得看:超过三分之一直接开数据库——379 个数据库密码加 122 个 Supabase service-role key;另有 103 个 Supabase 账号 token,能控制该账号下的每一个项目。剩下的大头是第三方服务和模型 API key,再加 43 个 SSH 登录、42 个 GitHub / GitLab / npm token、23 个 AWS key、6 个私钥。

注意这里有个很容易被忽略的点:没有一个是故意公开的。key 在命令里,命令被批准了,文件就跟着其他代码一起被提交了。整个过程里没有一步需要人做错决定。

4)为什么通用扫描器漏掉了 215 份

这是我认为整份报告里最该被记住的一节,因为它不只关于 Claude Code。

Apiiro 把 gitleaks 和 TruffleHog 对全部 104,185 份文件跑了一遍。在 1,122 份含真实凭据的文件里,gitleaks 报了 564 份,TruffleHog 报了 643 份,215 份两边都没报。

缺口在于「命令里的密码」。扫描器找的是 key 的形状:厂商前缀、长随机串。而 PGPASSWORD=、sshpass -p、mysql -p 后面跟的密码,或者 curl 里的 bearer header,在一条普通命令行里就是一个普通单词,两个工具基本都放过。

这个模式的影响面远大于这一个文件:只要 agent 记录了自己跑过什么,同样的密码就会出现在会话记录、记忆文件、交接文档里,而一个按 key 格式调优的扫描器在所有这些都是失效的。换句话说,你现在配的那套扫描,大概率覆盖不到这类泄露。

5)不止一个文件

allowlist 只是最清楚的一个例子。agent 还会把凭据写进别的、开发者会提交的文件里。

文件类型检查了多少含真实凭据的
团队共享的 .claude/settings.json76,011 份298 份
agent 会话记录(.specstory、aider、Cursor 导出)2,523 份87 个仓库里的 120 份
agent 记忆文件(MEMORY.md 一类)1,200 份9 个仓库
交接与部署笔记2,095 份33 份
VS Code Copilot 自动批准设置173 份9 个仓库
共享 settings.json 是团队故意提交的,所以它的凭据大多直接坐在 allow 规则里,被当成一条命令原文记着。

还有几个容易漏掉的出口:31,053 个 Hugging Face Space 里有 126 个持有这个文件,其中一个的 allow 规则里有一个数据库服务 key 和一个用户密码;GitLab.com 上抽查的 69 份公开副本里 2 份含真实凭据,其中一个是 GitLab access token。

即使文件一辈子没上 GitHub,开发机上也一样不安全。它就是项目文件夹里的明文 JSON。这个风险不是理论上的:2025 年 9 月的 Shai-Hulud npm 蠕虫会下载 TruffleHog 扫受害者磁盘——而 TruffleHog 恰好能报出这 1,122 份里的 643 份;再早三周,恶意的 Nx 包直接要求开发者本机的 Claude Code 和 Gemini CLI 去搜磁盘找凭据。文件也可能不经 git 就走:打进 npm 包、被 Docker 镜像里的 copy 命令带进去、装进 zip、落到某个同步盘。

6)11 月 7 日之后:官方回应和三个争议点

Apiiro 在发布前就把研究给了 Anthropic。文章上线后,Boris Cherny 于 11 月 7 日在 HN 上回应,周末发了十条评论。他的三条主张值得逐条看。

主张一:这个文件本该只留在本地,是我们自动 gitignore 的,所以这些是手工创建并提交的。

官方文档确实这么写:Claude Code 第一次在没忽略它的 git 仓库里创建该文件时,会把一条匹配 .claude/settings.local.json 的规则加进全局 git 排除文件。但「全局」正是问题所在——它不随仓库走。评论区有人直接追问:那为什么在 1,105 个仓库里有 539 个是 Claude 自己共同署名改动的?报告作者也指出文档里另一处写着:文件若已被 git 跟踪,其 allow 规则就要走 workspace trust 那一步,而用户不会被告知,下一次批准照样追加进那个被跟踪的文件。

主张二:权限规则是老一套了,大家都该用 auto 模式,更安全。

这是最有争议的一条。auto 模式从 2026 年 8 月 14 日起成为 Claude Code 在 Pro / Max / Team 上的默认,Anthropic 有一篇专门的文章讲他们怎么测的:内部红队、第三方红队、提示注入评测、1,053 名付费测试者的对照实验,结论是「每一项指标上都持平或更好」。这套论证本身站得住——人的注意力有限,是真实存在的攻击面。

但报告的评论区指出了同一个洞:auto 模式遇到敏感命令时同样会问,而那个提示的默认选项是「始终允许」,照样会把命令存下来。Cherny 的回应是「说得对,我们在想怎么改进」。所以换成 auto 模式并不自动解决这个问题,它只是把提问频率换成了另一种。

反方的核心诉求也很明确,一位评论者的说法很典型:我要的是确定性的边界,不是概率性的安全。如果 agent 能决定做某件事,那就没有任何东西阻止这件事被意外做出来。另一条高赞批评直接把「大家都该用 auto」读成对开发者的冒犯——因为人真正想要的是一条能改写的规则、一个能编辑的批准框,而不是只有「同意一次 / 永远同意 / 拒绝」三个按钮。

主张三:不管用什么 agent,都该开 secret scanning。

这条没错,但按第 4 节的结论,力度不够:你开的扫描器有将近两成的概率根本不报这类泄露。Cherny 自己也承认 Claude Code 历史上「没做凭据清洗」,理由是「有很多好工具做这件事」——而报告恰好证明了那些好工具在这里会漏。

他还提到团队在做的方向:让 auto 模式能抓到这种情况、内置基础凭据扫描,先做几个原型看权衡再上线。同时他两次提到 Anthropic 开源的 agent 无关沙箱 sandbox-runtime(7,029 星),说还会有更多改进。截至 11 月 6 日的最新版是 2.1.319,公开 changelog 里还看不到针对「allow 规则含凭据」的修复项。

还有一个细节不太好看:Claude Code 用户从 2025 年 8 月就在 GitHub 上报这个机制(#5544、#11982、#13106、#91270)。我逐个查了状态——四个全都关了,其中三个是被机器人以「不活跃」或「重复」自动关的,另一个(#91270,VS Code 扩展把永久授权写进被跟踪的 settings.json 而不是 settings.local.json)在 2026 年 11 月 5 日被以「不活跃太久」关闭,正好是报告上线那天。报告的说法是这些 issue 都没得到 Anthropic 的回复。

7)自查和清理:按这个顺序做

先把顺序说对,因为顺序错了会白干。先轮换,再清理——清掉文件不会清掉历史、fork 和 clone。

深夜书房桌上散落一地的纸片,一台笔记本电脑在旁发光
一个 allowlist 就是一个长期只增不减的抽屉,攒到几百条时没人再翻它。

个人开发者

  • 查公开性:在你的 GitHub 组织里搜 .claude/settings.local.json 和 .claude/settings.json。每一个命中都是现在任何人都能读的文件。
  • 查历史:就算文件今天已经删了,也去翻每个仓库的 git 历史。历史里有,每个 fork 和 clone 里就都有。
  • 如果被跟踪了:git rm --cached .claude/settings.local.json,然后把它加进仓库自己的 .gitignore。
  • 轮换:这个文件历史上持有过的每一个凭据都要换。
  • 就算从没提交过也要清理:打开文件,删掉每一条含凭据的规则。
  • 改习惯:别把密码打进命令,用 secrets manager 里的 $VAR。这样规则记下来的是 $DB_URL。
  • 改批准方式:批模式而不是批原文字面,比如 Bash(npm run:*)。这也是「别再问」的正确用法。

团队和安全负责人

  • 把 .claude/、.cursor/、.gemini/、.specstory/、agent 记忆文件当成 .env 同级别的敏感目录——笔记本上和仓库里都算。
  • 把它们加进 pre-commit 和 CI 的凭据扫描,包括历史;并且确认扫描器能抓 PGPASSWORD=、sshpass -p、mysql -p、bearer header 这类「命令里的密码」,而不只是厂商 key 格式。
  • 把 .claude/settings.local.json 加进组织的 .gitignore 模板。
  • 分诊优先级:先处理「凭据 + 公网主机」的组合,那些是真能直接用的。

小结

把这件事拆干净,其实只有一句话:你为了少被打断而攒下的那份「信任清单」,同时是一份明文凭据清单。它长在项目目录里,不进仓库的 gitignore,会被 agent 自己提交,躺了平均 217 天没人看,而且现有的扫描器有近两成看不出来。

Apiiro 这份报告的真正价值,是把它从「用户手滑」重新定义成「设计把两件事绑在了一起」——批准一条命令,和永久记下一个秘密,用的是同一次点击。设计层面的解法也清楚:规则记模式不记原文,密码走变量不走命令行。

对日常用 Claude Code、Codex、Cursor 的人来说,最该带走的三条是:先去自己的公开仓库里搜一遍 .claude/;扫描器要能抓命令里的密码;批准的时候看清楚那条命令里有没有一个叫密码的单词。前两条五分钟能做完,第三条每次都要做。

想在自己的机器上把几个顶级模型并排跑、对比它们的实际行为?可以看看俱乐部的 Claude Max / ChatGPT Pro 拼车方案。

参考来源

发表评论

邮箱不会公开,带 * 的是必填项。