一次"不再询问",把数据库密码写进了公网仓库:Claude Code 凭据泄露复盘

举报
xiayiyi0 发表于 2026/11/08 21:33:59 2026/11/08
【摘要】 Apiiro 扫描 GitHub 上 10 万余份 .claude/settings.local.json,在 1994 个公开仓库里找到 2539 个真实凭据。起因只是 Claude Code 的"不再询问"把带明文密码的命令原文写进了项目配置文件。本文还原机制、数据与扫描器漏报原因,并给出轮换、清理与写法层面的修复清单。

先说结论:这次的锅不在"AI 会不会写错命令",而在一个所有人都点过无数次的按钮——“Yes, and don’t ask again”。

起点是一个按钮

Claude Code 执行 Bash 命令之前会征求同意,三个选项里第三个是"Yes, and don’t ask again"。点下去之后,工具会把这条命令原封不动变成一条允许规则,落盘到项目根目录的 .claude/settings.local.json。

如果被批准的是 Bash(npm run:*) 这类模式,存下来完全合理。麻烦在于被批准的若是"一条具体命令",存进去的就是那条完整命令,一字不差,包括夹在里面的密码:

{
  "permissions": {
    "allow": [
      "Bash(PGPASSWORD='ExamplePassword123' psql -h db.example.com -U admin)"
    ]
  }
}

这串东西就躺在仓库里,等着下一次 git add . 把它带走。

Apiiro 数出来的账

11 月 5 日,安全公司 Apiiro 的研究员 Shmulik Cohen 和 Matan Giladi 把 GitHub 上公开的 .claude/settings.local.json 扫了一遍:104,185 份文件,分布在 103,617 个仓库。其中 1,122 份至少含一个真实凭据,合计 1,379 个。再把历史提交、fork,以及同目录下共享的 .claude/settings.json 一并算进来,总数是 2,539 个凭据、1,994 个仓库、63 家组织,以软件公司为主。

凭据类型 数量 为什么扎眼
数据库密码 379 大量是生产库,含一套 Azure Postgres 生产密码、某医疗记录库的 owner 密码
Supabase service-role key 122 绕过行级安全,等于全表读写权限
SSH 登录 43 部分直接是 root
GitHub / GitLab / npm token 42 能推恶意包,也能拉私有仓库
AWS 密钥 23 含某数据库厂商自己的管理员级云密钥
其他(Square token、私钥、包仓库 token 等) 约 1,930 兜底那一类

被点名的例子里还有一枚 Square 生产 token。63 家受影响组织里,有一家回信确认:扫描当时,那个密钥仍然有效。

为什么偏偏是它

同一次研究里,Apiiro 横向比了 16 款编码代理的权限实现。其他家要么把批准记录放在仓库之外,要么只存脱敏后的命令模式,要么压根没有"持久免打扰并写入项目文件"这个设计。只有 Claude Code 默认把整条命令原文写进工作树。

它也不是完全没做防护:第一次生成这个文件时,会往当前这台机器的全局 git excludes 里加一条。可这行保护不跟着仓库走,换个目录 clone 就失效了,更管不到在这个机制出现之前就已经提交上去的旧版本。

更值得琢磨的是另一半:Apiiro 能追溯的 1,105 个仓库里,有 539 个(接近一半)是这个代理自己提交的——提交作者行里带着 Claude 的共同署名。也就是说,它跑完命令、拿到批准、写好配置文件,然后顺手把配置文件一起提交了,人压根没注意。

扫描器为什么没拦住

Apiiro 把 1,122 份文件喂给了两个最常用的开源密钥扫描器:

  • Gitleaks 命中 564 份,漏掉 558 份;
  • TruffleHog 命中 643 份,漏掉 479 份;
  • 两个都没报的:215 份。

漏掉的基本是同一类写法:PGPASSWORD=…、sshpass -p …、mysql -u root -p…。这类密码没有 sk- 那种固定前缀,也没有云厂商密钥的固定结构,写不出正则,工具就沉默放行。何况它们在 shell 脚本里本来就是合法写法,误报成本高,扫描器宁愿不报。

而这些文件在默认分支上,最长的一次挂了 217 天。

这事其实早就有人提过

2025 年 8 月,anthropics/claude-code 的 issue #5544 就写着:带凭据的命令被以明文原样存储。2026 年 1 月 3 日,一个机器人以 “not planned” 关掉了它,理由是 60 天没有活动。截至 11 月 7 日,公开渠道里没看到 Anthropic 对这份研究的正式回应。

今天就能做的五件事

顺序很重要:先止血,再清理。

第一,先轮换,再清历史。删文件、改历史都不会让已经流出去的密码失效。AWS 密钥先去 IAM 禁用再重建,数据库密码先改掉,再谈别的。

第二,把它从索引里摘出来。

git ls-files .claude/settings.local.json
git rm --cached .claude/settings.local.json
printf '\n.claude/settings.local.json\n.claude/settings.json\n' >> .gitignore
git commit -m "chore: stop tracking Claude Code local settings"

第三,查历史,别只盯工作区。当前那份文件只是冰山一角。

git log --all -p -- .claude/ | grep -nE 'PGPASSWORD=|sshpass -p|mysql .*-p'
gitleaks git -v
trufflehog git file://. --only-verified

fork 也要一起看。Apiiro 的数据里,74 个仓库的 211 个 fork 都带着这些凭据的副本。

第四,从写法上断根:传变量,不传值。

export PGPASSWORD="$DB_PASSWORD"
psql -h db.example.com -U admin

这样被批准并存下来的是 $DB_PASSWORD,不是密码本身。生产环境再进一步,用密钥管理服务,只把引用传进去。

第五,把代理的配置目录纳入扫描。.claude/、.cursor/、.gemini/、.specstory/ 都该进 CI 的扫描清单和 pre-commit 钩子,而且必须扫 git 历史,不能只看当前工作区。

这不是某一家的事

任何把"用户批准过的操作"连同参数一起持久化进项目目录的工具,都有同样的形状,区别只在文件放在哪、存原文还是存模式。挑工具的时候,这算一个该看的指标。

说到底,这件事别扭的地方是它利用了一个很人性的东西:没人愿意在同一条命令上被问三次。安全设计如果只在"多问一句"和"少问一句"之间做取舍,最后一定有人拿便利换安全——而且是用一次点击,换掉一串生产密码。

先把 .gitignore 补上吧,五分钟的事。

参考资料:Apiiro 研究博客《Coding Agents Copy Your Secrets to a Hidden File》(2026-11-05);The Hacker News、CybersecurityNews 相关报道(2026-11-07);GitHub issue anthropics/claude-code #5544。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。