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 一类的东西对待。它不必进版本库,但必须进忽略规则和扫描规则。工具官方会不会在后续版本里默认把这些文件挪出项目目录,目前没有确切说法,在修复落地之前,防线只能自己搭。


如果觉得这篇文章对你有帮助,欢迎关注、点赞、收藏,你的支持是我持续创作的动力!

Logo

更多推荐