Claude Code 的「不再询问」把密码写进项目文件,GitHub 上 1122 份公开副本带密钥

安全公司 Apiiro 于 11 月 6 日发布研究称,Anthropic 的 AI 编程工具 Claude Code 会把用户批准的命令原文写入项目内的 .claude/settings.local.json,包括命令中出现的密码与令牌。研究人员在 GitHub 上找到 104,185 份该文件的副本,其中 1,122 份至少包含一个密钥,合计 1,379 个不同的凭证。

事件概述

Claude Code 默认在执行大部分 shell 命令前询问用户。当用户选择「是,且不再询问」(Yes, and don’t ask again)时,工具会把一条允许规则写入项目的 .claude/settings.local.json,后续匹配的命令不再询问。问题在于规则粒度:有时保存的是模式(如 npm test *),有时保存的是一条完整命令;当保存的是完整命令时,命令里输入的密码或令牌就一起以明文写入文件。

研究人员用最新模型 Opus 5.5 与 Fable 5.1 复现了这一行为,并在一家被通知的公司处得到确认:其密钥当时仍然有效。

技术细节:为什么删文件也没用

Claude Code 并非完全没做防护。它在首次写入该文件时,会把文件路径加入用户级的全局 git 排除列表,使本机所有 git 仓库都忽略它。但这个保护有两个漏洞:一是只存在于单个用户、单台机器的 git 配置里;二是它从不把该文件写进项目自身的 .gitignore,因此保护不会随项目传播。

结果就是一旦文件被提交过一次,后续所有克隆、fork 与历史记录都会带着它。研究数据里,539 个仓库(共 1,105 个被追溯的仓库)中,有一次提交把 Claude 列为共同作者并改动了该文件——研究人员据此判断,很多情况下是 Claude 自己把文件提交上去的。

Apiiro 的统计口径如下:

  • 公开 GitHub 上共 104,185 份 .claude/settings.local.json,分布在 103,617 个仓库。
  • 1,122 份(约 1.1%)含至少一个密钥,合计 1,379 个不同凭证。
  • 其中 379 个是数据库口令,超过三分之一可直接连入数据库;另有 43 个可登录公网服务器的 SSH 凭据、42 个 GitHub/GitLab/npm 令牌、23 个 AWS 密钥。
  • 涉及 63 家组织的仓库,多数为各规模软件公司。最严重的案例包括一家数据库厂商自身的管理级云密钥,以及一份病历数据库的 owner 口令。
  • 截至本周一,所有含密钥的文件仍在各自仓库的默认分支上;其中一半的仓库里,该文件已存在至少 217 天。
  • 历史版本中另有 734 个密钥分布在 509 个仓库,当前文件中已不存在,通常是文件被删除;211 个 fork(来自 74 个仓库)携带与原仓库相同的密钥。
  • 团队共享提交的 .claude/settings.json 中,76,011 份里有 298 份含密钥;把历史版本与 agent 聊天日志一并计入,总计 2,539 个密钥分布在 1,994 个仓库。

扫描器为什么没发现

研究人员用 gitleaks 与 TruffleHog(均关闭验证步骤)扫描了全部副本:1,122 份含密钥的文件中,gitleaks 命中 564 份,TruffleHog 命中 643 份,两者都没命中的有 215 份。漏报集中在「命令行里的密码」——扫描器依赖已知密钥格式,而 PGPASSWORD=、sshpass -p、mysql -p 后面的口令看起来只是普通命令里的普通单词。

另一个值得注意的对比:在 16 个被比对文档的编程 agent 中,只有 Claude Code 被确认会默认把完整批准命令保存进项目文件。

影响评估

这批泄露的特点不是「量大」而是「位置尴尬」:密钥躺在源码仓库里,与代码一起被克隆、被 fork、被 CI 拉取,任何有仓库读权限的人都能直接取用。379 个数据库口令意味着大量案例可以直接连库,而不是只能读代码。研究人员没有验证这些密钥当前是否仍然有效,但那家被通知的公司已确认其密钥处于有效状态。

更麻烦的是历史版本与 fork:即使运营者今天删掉文件,git 历史、fork 副本、以及被打进 npm 包、Docker 镜像、zip 归档或同步目录的副本仍然携带密钥。所以处置顺序必须是先轮换、再清理。

修复建议

针对开发者个人:

  • 在本机打开 .claude/settings.local.json(即使从未提交过),删除所有包含密钥的允许规则。
  • 停止跟踪该文件:git rm –cached .claude/settings.local.json,并把 .claude/settings.local.json 加进项目自身的 .gitignore。
  • 改用变量引用密钥,例如 $DB_URL,让保存下来的规则只含变量名;批准模式(如 Bash(npm run:*))而不是单条完整命令。

针对团队与安全团队:

  • 在 GitHub 组织内搜索 .claude/settings.local.json 与 .claude/settings.json,并检查每个仓库的 git 历史,包括文件已被删除的仓库。
  • 先轮换这些文件曾承载过的全部密码、令牌与密钥,再执行清理;顺序反了等于没做。
  • 把 .claude/、.cursor/、.gemini/、.specstory/ 以及各 agent 的记忆文件按 .env 同级对待,纳入 pre-commit 与 CI 的密钥扫描,并覆盖历史提交。
  • 优先排查与公网服务器配对的凭据,这类凭据的利用门槛最低。
  • 把 .claude/settings.local.json 加入组织级 .gitignore 模板,避免同类问题在新仓库复现。

补充一个背景:早在 2025 年 8 月 11 日就有用户在 Claude Code 仓库提交过同类问题(issue 5544),报告批准过的含密钥命令被逐字存入 settings.local.json,该报告在 60 天无活动后被机器人以「not planned」关闭。研究人员引用的四个相关报告目前也均已被同一机器人关闭。

参考来源