如果你在用 Claude Code 或其他任何 AI 编码 Agent,先花十分钟检查你的仓库:搜索被提交上去的 Agent 设置文件,轮换其中包含的所有凭据,并开启 GitHub 的 secret scanning。被盗的密钥转手很快(Unit 42 描述过一个只用了几分钟的案例),所以先检查,再看故事。
先检查你的仓库
先从 Claude Code 事件的核心文件说起:.claude/settings.local.json。安全公司 Apiiro 在 103,617 个公开仓库中发现了 104,185 份该文件的副本,其中 1,122 份含有至少一个密钥(The Hacker News)。下面教你怎么查自己的仓库。
- 按文件名搜索你的 GitHub 组织(换成你自己的组织名,需要 GitHub CLI)。
- 检查你参与的每个仓库是否曾提交过该文件,包括旧分支。最新一次提交删掉的内容,历史记录里依然保留。
- 查看已保存的授权记录,留意任何像密码、token 或连接字符串的内容。
- 扫描完整的提交历史,并增加一条针对命令中直接输入密码的规则,默认规则集往往会漏掉这类情况。
# 1. Find the files across an organization
gh search code --owner YOUR-ORG --filename settings.local.json
# The same search in the GitHub web search box
# org:YOUR-ORG path:.claude/settings.local.json
# 2. Was it ever committed, on any branch?
git log --all --oneline -- .claude/settings.local.json .claude/settings.json
# 3. List what you have approved with "don't ask again"
jq -r '.permissions.allow[]' .claude/settings.local.json
# 4. Grep the folder and its history for the usual suspects
grep -rEn 'PGPASSWORD=|sshpass -p|mysql -p|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,}' .claude/
git log --all -p -- .claude | grep -E 'PGPASSWORD=|sshpass -p|mysql -p|AKIA[0-9A-Z]{16}'这些都是标准的 git、jq 和 grep 命令,但输出结果只能当作线索,不能当作定论。GitHub 的代码搜索只能看到分支当前的内容,所以才需要第 2 步。
扫描工具方面,Apiiro 用的是 Gitleaks 和 TruffleHog。先对历史记录跑一遍,再加一条自定义规则,因为默认规则能抓到密钥,却抓不到跟在命令行参数后面的密码。
gitleaks git --redact -v .
trufflehog git file://.# .gitleaks.toml
[extend]
useDefault = true
[[rules]]
id = "password-inside-command"
description = "Password passed on a command line"
regex = '''(PGPASSWORD=|sshpass -p |mysql -p)\S+'''这条自定义规则很有必要。Apiiro 用两个工具扫描了它找到的所有文件:在 1,122 个含密钥的文件中,gitleaks 标记出 564 个,TruffleHog 标记出 643 个,两者都没发现的有 215 个,多数是跟在 PGPASSWORD=、sshpass -p 或 mysql -p 后面的密码(The Hacker News)。
Apiiro 还建议安全团队把 .claude/、.cursor/、.gemini/、.specstory/ 和 Agent 记忆文件当作会携带密钥的文件,与 .env 同等对待。我们建议对团队装过的每个 Agent 都做同样的搜索:Cursor、GitHub Copilot 或 Codex 保存的对话记录里,同样可能留着粘贴进去的密钥。
一次点击授权,如何把密码写进你的仓库
Claude Code 在运行大多数 shell 命令前都会先问你。选择"Yes, and don't ask again",它就会在 .claude/settings.local.json 中写入一条 allow 规则。Anthropic 的文档说,以这种方式做出的 Bash 授权会"按仓库和命令永久有效"(Claude Code docs)。问题出在规则的形式:它可以是 npm test * 这样的模式,也可以是完整的原始命令;而当完整命令里带着密码时,密码就以明文保存下来了。

这个文件本该只留在本地。根据 Anthropic 的设置文档,Claude Code 在尚未忽略该文件的仓库里首次写入它时,会把 **/.claude/settings.local.json 加入你的全局 git excludes;而手动创建的文件,则需要你自己在 .gitignore 里加一行(Claude Code docs)。这只保护了一台电脑,对新克隆的仓库、手动创建的文件,或在忽略规则存在之前就已提交的文件都没用。
Apiiro 发现的密钥中,超过三分之一能直接打开数据库,其中包括 379 个数据库密码。其余的包括 43 个公网服务器的 SSH 登录凭据、42 个 GitHub、GitLab 和 npm 的 token,以及 23 个 AWS 密钥。受影响文件分属 63 个组织,在含密钥的仓库中,有一半的文件至少已存放了 217 天。Apiiro 没有测试这些密钥是否仍然有效,不过它通知过的一家公司确认其密钥当时确实还有效。
Anthropic 到目前为止的回应,只是一条 Hacker News 评论
2025 年提交的一份 bug 报告,在 60 天无活动后被自动机器人以"not planned"关闭;Apiiro 的研究人员称,他们引用的相关报告都没有得到 Anthropic 的回复(The Hacker News)。公开回应出现在 Hacker News 上,来自一位自称"Boris from the Claude Code team"的评论者。他写道,"Historically Claude Code has not done secret scrubbing"(Claude Code 过去一直没有做密钥清理),团队正在"looking into what we can do to prevent this"(研究如何避免这种情况),并称这次的情况是"the files were manually created and checked in"(文件是手动创建并提交的)。他还补充说:"Permission rules are old hat. Everyone should be using auto mode instead."(权限规则已经过时了,大家都应该改用 auto 模式。)
Apiiro 的数据与"手动创建"的说法对不上。在它追踪的 1,105 个仓库中,有 539 个仓库里,曾有一次把 Claude 列为共同作者的提交修改过该文件。共同作者一行并不能证明是谁运行了 git,但这很难与"有人手动创建文件"的说法相吻合。相比那条评论,我们觉得提交数据更难被驳倒;在 Anthropic 另有说明之前,我们会认为 Claude Code 有可能提交这个文件。
Grok Build 泄露了两次,方式各不相同
Grok Build 的两件事经常被混为一谈,但它们需要不同的修复方式。(媒体报道里把这家公司称为 SpaceXSI 或 SpaceXAI。)
第一件发生在 2026 年早些时候。一位以 cereblab 署名发布文章的研究者分析了 Grok Build 0.2.93,报告称它会把整个被跟踪的 Git 仓库,连同提交历史、已提交的密钥和 API key 一起上传到云存储(The Next Web)。据该分析,上传的数据量大约是任务所需的 27,800 倍,而隐私开关据称毫无作用。xAI 当时宣传这个工具时声称"nothing from your codebase transmitted to xAI servers during a session"(会话期间不会向 xAI 服务器传输任何代码库内容)。Elon Musk 承认了这些传输,并表示会删除此前的用户数据。TNW 补充说,目前没有独立审计确认数据已被删除。
第二件是现在的新闻,它与代码仓库无关。安全公司 Claroc 发现,Grok Build 的分享链接可以被从未收到过链接的人找到(Claroc)。Engadget 报道称,每个链接使用由四个单词组成的随机短语,熵最高 38 位,放在"to the left of the domain"(域名左侧)(Engadget)。也就是说它位于主机名中,Claroc 解释说,被动 DNS 服务会收集这些查询记录。而 ChatGPT 和 Gemini 的分享对话把秘密放在 URL 路径里,DNS 看不到路径。Claroc 写道:"没人会去猜,也没必要猜,因为数据集里直接列出来了。"
暴露的应用里确有实质内容。Claroc 列出了一份包含 594 个战略客户的销售区域地图、八份 X 广告提案、四个正在使用的广告主账户看板、一家医院集团的员工应用(名册上有 3,500 多名员工),以及一个消防部门的实时排班看板(含每位消防员的姓名和警号)。在它测试的地址中,94.7% 无需登录即可打开。Claroc 估计,大约 2% 的应用包含足以评为高危或严重级别的内容。
Claroc 称,SpaceXSI 下线了其员工构建的 24 个应用,"但没有下线其他 Grok 用户和组织的应用"。Engadget 补充说,据报道该公司直到 Claroc 在 X 上@了 Musk 和另一位公司高管之后才有回应。Claroc 的文章称,SpaceXSI 回答了一位记者的提问,表示 Grok 应用在创建者选择分享之前都是私有的,但没有回复这份报告本身。我们没有找到任何声明说其他应用会被下线。
对用户来说,解决办法很直接:凡是通过 Grok Build 构建或分享的内容,都当作公开的;不需要的链接就撤掉;任何接触过你分享过的应用的凭据都要轮换。这里加 .gitignore 没有任何帮助。
被盗的密钥几分钟内就会被转卖
Palo Alto Networks 的 Unit 42 描述了一种"token jacking":被盗的 AI API key 通过灰色市场的"中转站"转卖,这些中转站以折扣价出售前沿模型的访问权限(Unit 42)。其列出的密钥来源包括暴露的代码仓库、infostealer 窃密木马和被投毒的 npm 包。在一个案例中,攻击者几分钟内就把暴露的凭据接入了中转站,在被发现之前产生了"nearly a million dollars in charges"(近一百万美元的费用)。Unit 42 称,受害者"very little recourse to recover funds"(几乎没有途径追回资金)。它给出的防御措施很朴素:设置带告警的消费上限,用短期 token 取代长期密钥,并审查每一个能调整额度的特权账户。
修复泄露:忽略、取消跟踪、再轮换
先轮换,再清理。已吊销的密钥在所有 fork 和缓存副本里都是废的,而只是从最新提交里删掉的密钥,在历史记录里依然有效。GitHub 的建议是立即轮换已暴露的凭据,并称清理历史记录"time-intensive and often unnecessary if you've already revoked the credential"(耗时,而且如果你已经吊销了凭据,往往没有必要)(GitHub docs)。
# Rotate the exposed credentials first (table below), then:
# Stop tracking the local file (keeps your copy on disk)
git rm --cached .claude/settings.local.json
# Add to the repo's OWN .gitignore, not just your global excludes
printf '%s\n' '.claude/settings.local.json' '.specstory/' >> .gitignore
git add .gitignore
git commit -m "Stop tracking agent local settings"
# Drop any allow rule that looks like it holds a secret, even if never committed
jq '.permissions.allow |= map(select(test("PGPASSWORD|password|token|secret|key"; "i") | not))' \
.claude/settings.local.json > .claude/settings.local.json.new \
&& mv .claude/settings.local.json.new .claude/settings.local.json在整体忽略 .cursor/ 或 .gemini/ 之前先想一想,因为有些团队会特意把共享规则提交在这些目录里。
主流服务商的密钥轮换
每个服务商的控制台都有对应的按钮,安全的顺序是:先创建替换密钥,部署上线,再停用旧密钥。先删除可能会让生产环境宕机。
| 服务商 | 轮换方式 | 备注 |
|---|---|---|
| AWS | 创建第二个 IAM access key,更新你的应用,检查 last-used,停用旧密钥,再将其删除 | aws iam create-access-key、get-access-key-last-used、update-access-key --status Inactive、delete-access-key(AWS docs) |
| GitHub | 删除 token,并创建一个带有效期的新 token | GitHub 会自动吊销推送到公开仓库或 gist 的 token;如果泄露发生在其他地方,你可以通过 REST API 提交吊销请求(GitHub docs) |
| Stripe | 在 Dashboard 中轮换密钥,今后使用受限密钥(restricted keys) | 即使不确定是否有人看到过也要轮换;Stripe 可能会停用它检测到的密钥(Stripe docs) |
| OpenAI | 创建替换密钥,更新你的应用,确认可用后再吊销旧密钥 | 为项目密钥设置有效期,应用使用项目服务账号(OpenAI docs) |
表中没有的服务商,包括 Anthropic 和 xAI,我们没有验证过其轮换步骤,请按它们各自的文档操作。至于数据库密码和 SSH 登录凭据,也就是 Apiiro 发现的大头,要修改密码、结束所有已打开的会话,然后从文件首次被提交的那天起检查访问日志。
开启 GitHub 的扫描功能,并了解它的局限
Secret scanning 会检查每个分支的完整历史,以及 issue、pull request、discussion 和 wiki。它在公开仓库上免费且自动运行;组织内的私有和内部仓库则需要 GitHub Secret Protection(GitHub docs)。当检测到合作伙伴的密钥格式时,GitHub 会通知该服务商,对方可能直接吊销密钥,而你的仓库里不会有任何提示。
Push protection 这一层,本可以在许多此类文件公开之前就拦住它们。它会阻止包含已检测到密钥的推送。根据 GitHub 文档,用户级设置对推送到 GitHub.com 公开仓库的操作默认开启,而仓库级设置默认关闭,需要仓库管理员、组织所有者或安全管理员来开启(GitHub docs)。它会扫描服务商模式、私钥和连接字符串之类的通用模式、你的自定义模式,以及一个由 AI 检测、针对密码等非结构化密钥的类别(公开预览中)。最后这一类看起来正是冲着 Apiiro 发现的那个缺口去的。通过 REST API 推送不会触发它;命令行推送、网页端提交和文件上传则会。
也要了解它的绕过机制。默认情况下,任何有写权限的人都可以选一个理由来绕过拦截,包括"I'll fix it later",这会创建一条告警并给所有者发邮件。如果你的 Agent 代你提交代码,请设置委派绕过(delegated bypass),让只有指定的人才能放行含密钥的推送。
今天就该修改的 Agent 权限设置
三个习惯就能让密码不进入那个文件。授权时用模式,绝不要授权带凭据的完整命令。把密钥放进环境变量,这样命令里带的是名称而不是值。从一开始就禁止 Agent 访问存放密钥的文件。Anthropic 的设置文档给出了写法:
{
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)"
]
}
}你的 Agent 该运行的命令形如 psql "$DATABASE_URL",这样授权规则里存的只有一个变量名,别无其他。变量请从密钥管理器或 shell 配置文件中加载,不要从 Agent 能读到的文件里加载。
文档里有两个细节对团队很重要:已提交的 .claude/settings.json 中的 allow 规则要等每位队友信任该文件夹后才生效,而 deny 和 ask 规则立即生效;auto 和 bypassPermissions 模式不会从项目或本地设置中生效。组织可以用 allowManagedPermissionRulesOnly 锁定 allow 规则。前面引用的那位 Claude Code 团队成员说,有了 auto 模式就不需要权限规则了。我们仍然会保留 .env 的 deny 规则:只要两行,文件一提交就生效。
其他工具的设置名称不同,但要问的问题是一样的:这个工具把授权记录、历史或对话记录存在哪里,那个位置是否在某个仓库里?按文档来看,在 Apiiro 对比的 16 个编码 Agent 中,Claude Code 是唯一一个确认默认会把完整的已授权命令保存到项目文件里的。其余十五个仍可能通过对话记录、粘贴的密钥和记忆文件泄露。我们的终端编码 Agent 对比和 Cursor vs Copilot vs Windsurf 两篇文章介绍了各个工具都存了什么。
最可靠的控制手段不依赖 prompt。把 Agent 放在容器或沙箱里运行,只给它该任务所需的凭据,范围要小,有效期要短。具体做法我们写在了如何约束 AI Agent里。
轮换是用户的责任,默认设置是厂商的责任
把密码输进命令里是用户的失误,轮换也该由用户负责。但一个工具把完整命令存进项目文件,又靠你主目录里的一条 exclude 来保密,这种保护只在一台笔记本上有效。

默认设置就是政策,而这次正是默认设置决定了结果。如果 Claude Code 在保存规则前先清除密钥,或者把忽略规则写进仓库自己的 .gitignore,就能从源头堵住这个漏洞。Anthropic 说正在研究(我们希望看到一个时间表)。一份被不活跃机器人关闭的 bug 报告并不让人放心,而提交数据也与"手动创建"的说法对不上。
在分享链接这件事上,Grok Build 是更糟的情况,因为用户什么都没做错。一个任何人都能通过被动 DNS 找到的链接,无论分享菜单暗示了什么,都不算私有。而此前的上传行为,也与一句说法相反的宣传语自相矛盾。在分享链接问题修复、外部审计确认数据已删除之前,我们不会把客户名单、销售区域地图或生产环境凭据放进 Grok Build。如果情况有变,我们的看法也会变。
把 Agent 目录当作 .env 文件对待,花一个下午把上面的设置做好。你可以在我们的 AI 编码助手榜单上对比这些工具本身,也可以在 AI 网络安全工具榜单上了解生态里的安全工具。
团队检查清单
把目前符合的项勾上,得分会告诉你从哪里开始。两分的项目,是我们会在午饭前就做完的。
常见问题
.claude/settings.local.json 可以安全地提交吗?
不可以。它保存着你个人的授权记录,里面可能包含凭据。Anthropic 的文档说,Claude Code 创建该文件时会通过全局 exclude 让它不进入 git,而手动创建的副本需要你自己加忽略规则。把它加进每个仓库的 .gitignore,如果曾经提交过,就用 git rm --cached 取消跟踪。
仓库是私有的,还需要轮换密钥吗?
需要,只要这个文件里曾经存过凭据。私有仓库会被克隆到笔记本和 CI runner 上,被 fork 到其他账号,有时还会被改成公开。GitHub 对组织私有仓库的 secret scanning 需要付费方案,所以你的仓库可能一直没有自动检查。
从仓库里删除文件,密钥就没了吗?
只是从最新提交里删掉了,别的都还在。历史记录、fork 和缓存都保留着旧版本,Apiiro 就发现了 734 个只出现在该文件旧版本中的密钥。先轮换凭据,只有想让仓库整洁时才需要重写历史。
GitHub Copilot、Cursor 和 Codex 也有同样的问题吗?
在 Apiiro 对比的 16 个 Agent 中,只有 Claude Code 被确认存在这种行为:默认把完整的已授权命令保存到项目文件中。但这并不意味着其他工具没问题。任何会把对话记录或记忆写进你工作目录的 Agent,都需要同样的忽略规则。