Claude Code 把数据库密码写进了项目文件:GitHub 上躺着 2539 个真实凭据
2026 年 11 月 9 日,Ars Technica 报道了安全公司 Apiiro 的一项发现:超过 10 万份 Claude Code 本地设置文件被上传到了公共仓库,其中约 1100 份包含数据库密码、API Token、私钥或 root 登录信息。研究人员把 Git 历史记录、共享设置文件和智能体聊天记录放在一起比对,在 1994 个代码库里识别出 2539 个真实凭据,波及 63 个组织,从学生团队到初创公司都有。IT之家转述的报道里提到,官方正在修复。
这里没有出现模型被攻破的环节。泄露路径更朴素:AI 编程工具为了方便,会把本地状态写进项目目录,而项目目录跟着 git 走。开发者执行一次 git add .,配置文件就进了公共仓库。用 Java、Go、Python 还是 TypeScript 都一样,问题不在语言栈,而在版本控制的边界。
设置文件为什么会跑到公共仓库
AI 编程智能体要在项目上下文里工作,就得读项目目录下的配置:接哪个模型端点、挂哪些 MCP server、调试用哪个代理、本地数据库连哪里。这些信息天然带着环境细节,有的还带鉴权头。工具把它们落在项目目录里,逻辑上说得通,因为下次打开项目还要用。
麻烦在于这些文件长得不像密钥。它们不在 .env 里,文件名也不带 secret,很多人下意识觉得“这就是个工具配置”。而 .gitignore 是手写的,模板里没有这一条,第一次初始化仓库时没人会想到补上。等到 CI 跑起来、仓库设成 public,凭据已经进了提交历史。
一旦进入公共仓库,就要按已泄露处理。GitHub 上的爬虫扫公开仓库的速度以分钟计,删掉当前文件不会让历史里的 blob 消失。
先自查:三步找出已经漏出去的凭据
第一步,看当前被跟踪的文件里有没有智能体留下的本地配置。下面这条命令是演示用法,目录名按你实际用的工具替换:
git ls-files | grep -E '(^|/)\.(claude|cursor|aider|continue)/'
有输出就说明这些文件正在被版本控制。特别注意名字里带 local 或 settings 的那些。
第二步,翻历史,看它们是不是曾经出现过又被删掉:
git log --all --name-only --pretty=format: \
| sort -u \
| grep -iE 'settings\.local\.json|\.env($|\.)|credentials'
--all 不能省。只在当前分支上看,会漏掉已经合并过的侧分支。
第三步,用专门的扫描器过一遍内容,而不是靠文件名猜:
gitleaks detect --source . --redact --report-format json --report-path gitleaks.json
--redact 让输出里的密钥被打码,方便把报告贴进工单而不二次扩散。报告出来之后,处置顺序是固定的:先轮换,再清理。数据库密码改掉、Token 吊销重发、私钥换对,这些动作要在清理历史之前完成。清理只是让后来者扫不到,对已经抓走数据的人没有任何约束力。
真要清理历史,git filter-repo 比 filter-branch 稳:
git filter-repo --path .claude/settings.local.json --invert-paths
这条命令会重写提交哈希,所有协作者都得重新克隆,远端要强制推送。仓库有 fork 或已被镜像的话,清理效果有限。这也是为什么轮换永远排在第一位。
把拦截前移:pre-commit 和 CI 各管一段
密钥扫描放在提交前最省钱。用原生 Git hook 就行,不依赖额外框架,也不涉及版本号对齐:
#!/usr/bin/env bash
## .git/hooks/pre-commit 演示用法
set -euo pipefail
PATTERN='(sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{36}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----)'
staged=$(git diff --cached --name-only --diff-filter=ACM)
[ -z "$staged" ] && exit 0
if echo "$staged" | xargs -r grep -nEI "$PATTERN"; then
echo "检测到疑似凭据,提交已阻断。改用环境变量或密钥管理服务。" >&2
exit 1
fi
正则覆盖的是几类常见前缀:OpenAI 风格的 sk-、GitHub 个人访问令牌的 ghp_、AWS access key ID 的 AKIA、PEM 私钥头。它拦不住自定义格式的内部 Token,所以只能当第一道网。
第二道放在 CI 或服务端。GitHub 的 secret scanning push protection 能在推送阶段直接拒绝含已知格式密钥的提交,前提是仓库开了这个功能。自建流水线里补一条:
## 演示用法
- name: scan secrets
run: gitleaks detect --source . --redact --exit-code 1
--exit-code 1 让有发现时构建失败,否则扫描结果只会躺在日志里没人看。
.gitignore 那一行也要补,越早越好:
.claude/
.cursor/
.env
.env.*
!.env.example
注意最后一行。把示例文件留在版本库里,团队才知道需要配哪些变量,这比在 README 里列一遍更不容易过期。
踩坑点与取舍
最常见的坑是只加 .gitignore。它只对未跟踪文件生效,已经提交过的文件继续被跟踪,改规则没有任何作用,得先 git rm --cached。
第二个坑是把“配置文件”和“密钥”分开看。AI 工具的本地设置里往往嵌着 MCP server 的鉴权头、内部服务地址、代理凭据。单个字段看起来不敏感,拼起来足够做横向移动。条目里提到的“智能体聊天记录”同理,对话里贴过的连接串也会落盘。
第三个坑是误报治理。上面那个 hook 会把单元测试里的假 key 一起拦下,团队被挡几次就会开始用 --no-verify 绕过,防线等于没有。要么维护 allowlist,要么把测试夹具改成明显不合法的格式,比如把长度截短。
适用场景上,十人以内、仓库不多的小团队,.gitignore 加 pre-commit hook 基本够用,成本接近零。有合规要求或者仓库数量上三位数的,就得把扫描放进 CI 并接上集中式密钥管理,让开发者的本地文件里根本不出现长期凭据。代价是本地调试要多一步注入,新人上手会慢半天。
真正该改的习惯是:把 AI 智能体在项目里留下的状态目录,当成 node_modules 一类的东西对待。它不必进版本库,但必须进忽略规则和扫描规则。工具官方会不会在后续版本里默认把这些文件挪出项目目录,目前没有确切说法,在修复落地之前,防线只能自己搭。
如果觉得这篇文章对你有帮助,欢迎关注、点赞、收藏,你的支持是我持续创作的动力!
更多推荐



所有评论(0)