Claude Code 安全失败案例7选 | 真实事故与防范措施
介绍7个Claude Code中实际发生的安全事故:.env泄露、生产数据库误操作、计费爆炸等,逐案解析原因与防范代码。
“Claude Code 很方便,但感觉有点可怕” —— 这种感觉是对的。强大的工具会造成强大的事故。
本文介绍在使用 Claude Code 开发时实际容易发生的7个安全事故案例,结合具体代码和配置,详解为何发生·如何防范。请将他人的教训引以为戒。
案例1:.env 文件被 push 到 GitHub
发生了什么
“这个仓库,想把环境变量传给 CI,请把 .env 也一起 commit” 这个指令传给了 Claude Code。Claude Code 忠实地执行了 git add .env && git commit。push 到 GitHub 后几分钟,爬虫检测到了 API 密钥。Slack 上收到了 “Your API key has been exposed” 的通知。
原因
.gitignore中没有包含.env- Claude Code 会忠实地执行 “commit 这个” 这类指令
- 用户也顺手通过了确认界面
对策代码
1. 自动化项目创建时的安全设置
# scripts/init-security.sh — 每次创建项目时必须执行
#!/bin/bash
cat >> .gitignore << 'EOF'
# === 安全:绝对不 commit ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF
echo "✓ 已向 .gitignore 添加安全排除模式"
git add .gitignore && git commit -m "security: add .gitignore patterns"
2. 用 Hook 在 commit 前扫描
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 您正在尝试 stage .env 文件!请中止!' && exit 1 || exit 0"
}]
}
]
}
}
3. 已经 push 的情况下的处理
# Step 1:首先立即轮换 API 密钥(最优先)
# Step 2:从 git 历史中完全删除
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Step 3:强制 push 到远端
git push origin --force --all
# Step 4:清除 GitHub 缓存(也联系 GitHub 支持)
案例2:对生产 DB 执行了 DROP TABLE
发生了什么
“这个表不用了,删除掉” 的指令下,Claude Code 生成并执行了 DROP TABLE old_users;。问题在于这连接的是生产环境的 DATABASE_URL。备份只有3天前的,3天的数据消失了。
原因
- 开发环境和生产环境共用同一个
.env - Claude Code 无法区分环境
- 虽然设置了
ask,但顺手按了 “OK”
对策代码
1. 每个环境的 .env 完全分离
.env.development # ← 本地开发用,测试 DB
.env.staging # ← 预发布,生产的副本
.env.production # ← 生产,手动管理·禁止共享
2. 在脚本中内置环境检查
// scripts/db-migrate.mjs
const env = process.env.APP_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL ?? "";
if (env === "production") {
const readline = require("readline").createInterface({
input: process.stdin, output: process.stdout
});
await new Promise((resolve) => {
readline.question(
`⚠️ 即将连接到生产 DB (${dbUrl.split("@")[1]})。\n确认要继续吗?(输入 yes):`,
(answer) => {
readline.close();
if (answer !== "yes") { console.log("已中止。"); process.exit(0); }
resolve(undefined);
}
);
});
}
3. 在 CLAUDE.md 中禁止生产操作
## 🚨 生产环境禁止事项
当 DATABASE_URL 中包含 `prod` `production` `live` 时:
- 绝对不执行 DROP / TRUNCATE / DELETE(无 WHERE 子句)
- 迁移前必须获取用户确认
- 执行前提示备份获取命令
案例3:rm -rf 删除了重要文件
发生了什么
“清理 build/ 目录” 的指令,由于路径指定错误变成了 rm -rf ./,删除了整个项目。git 管理范围外的文件(本地配置文件、未 commit 的实验代码)再也无法找回。
原因
rm -rf是 Claude Code 最危险的命令之一- 路径没有双引号 → 含空格的路径导致误动作
- 确认时 “随便吧” 地通过了
对策代码
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~*)",
"Bash(rm -rf .*)"
],
"ask": [
"Bash(rm -rf*)"
]
}
}
再用 Hook 在删除前显示内容:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ 检测到删除命令。5秒后执行。按 Ctrl+C 中止。' && sleep 5"
}]
}
]
}
}
案例4:API 密钥直接写在 prompt 中传给 subagent
发生了什么
“使用 QIITA_TOKEN=abc123def456 发布到 Qiita” 直接写在 prompt 中并委托给 subagent。subagent 有时会把内容写入日志或内存,token 就残留在了 .claude/ 目录下的日志文件中。
原因
- prompt 会作为对话历史保留
- 传给 subagent 的 prompt 同样也会保存在日志中
- 即使是本地环境,也存在通过其他进程或备份泄露的风险
对策代码
绝对不写在 prompt 中,通过环境变量传递
# ❌ 危险
claude -p "使用 QIITA_TOKEN=abc123 执行 qiita-publish.mjs"
# ✅ 安全:在脚本侧从 process.env 读取
# 在 .env 中写 QIITA_TOKEN=abc123
claude -p "执行 scripts/qiita-publish.mjs(token 从 .env 自动读取)"
传给 subagent 的指令也同样
// ❌ 危险
Agent({ prompt: `使用 API 密钥 ${process.env.SECRET_KEY} 来...` });
// ✅ 安全:只传密钥名称,值在脚本侧读取
Agent({ prompt: "使用 SECRET_KEY 环境变量来..." });
案例5:API 调用无限循环导致计费爆炸
发生了什么
“出错的话自动重试” 的指令下,编写了带有错误处理的脚本。在错误永远无法解决的情况下重试不停,1小时内调用 Anthropic API 3000次,产生了 $200 的费用。
原因
- 没有设置重试上限
- 没有 exponential backoff,以1秒间隔无限循环
- 没有设置计费告警
对策代码
// utils/retry.ts — 安全的重试工具
export async function withRetry<T>(
fn: () => Promise<T>,
options = { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err as Error;
if (attempt === options.maxAttempts) break;
// Exponential backoff + jitter
const delay = Math.min(
options.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
options.maxDelayMs
);
console.warn(`Attempt ${attempt}/${options.maxAttempts} failed: ${err.message}`);
console.warn(`Retrying in ${Math.round(delay / 1000)}s...`);
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error(`重试 ${options.maxAttempts} 次后仍然失败:${lastError!.message}`);
}
在 CLAUDE.md 中明确规定:
## API 调用时的必须规则
- 重试最多3次
- 必须实现 Exponential backoff(1s → 2s → 4s)
- 不创建无限循环:禁止 while(true) + API 调用
案例6:git push --force 删除了同事的 commit
发生了什么
“用本地状态覆盖远端” 的指令下执行了 git push --force。团队成员刚刚 push 的3个 commit 消失了。那个成员本地也没有变更,代码彻底丢失。
原因
--force容易在不理解危险性的情况下执行- Claude Code 会忠实地执行 “覆盖远端” 这类指令
- 不知道
git push --force-with-lease这个安全替代方案
对策代码
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(git push --force *master*)",
"Bash(git push --force *main*)",
"Bash(git push -f *master*)",
"Bash(git push -f *main*)"
]
}
}
在 CLAUDE.md 中指定替代命令:
## git 操作的安全规则
- `git push --force` 是**禁止**的
- 改用 `git push --force-with-lease`
(如果有他人的变更会自动被拒绝)
- 直接 push 到 main/master 分支前必须获取用户确认
案例7:权限过多的服务账号访问了所有资源
发生了什么
“使用这个 GCP 服务账号密钥操作 Cloud Storage” 的指令下,使用了拥有 Owner 权限的服务账号。Claude Code 不仅访问了 Cloud Storage,还以 “调查” 为由连接了 BigQuery·Cloud SQL·GKE 集群,产生了意外费用。
原因
- 服务账号被赋予了超过必要的权限(违反最小权限原则)
- Claude Code 具有积极使用工具的特性
- “为了调查” 这个正当理由也导致了大范围访问
对策代码
创建最小权限的服务账号:
# ❌ 应避免:Owner 权限
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/owner"
# ✅ 只赋予必要的最小权限
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectAdmin"
# ← 只有 Cloud Storage 的读写权限
在 CLAUDE.md 中明确访问范围:
## GCP 访问限制
本项目使用的服务账号权限:
- Cloud Storage:读写 OK(存储桶:仅 my-project-assets)
- BigQuery:禁止
- Cloud SQL:禁止
- 其他 GCP 资源:禁止
试图访问权限范围外资源的指令一律拒绝。
防止事故的综合检查清单
从这7个案例中提取共同模式的最终检查清单。
### 当天应该完成的设置(30分钟)
- [ ] 在 .gitignore 中添加 .env 模式
- [ ] 在 .claude/settings.json 中添加 deny 列表(rm -rf、git push --force、DROP TABLE)
- [ ] 在 CLAUDE.md 中明确记录禁止事项
### 每周应该确认的事项
- [ ] 通过 git log 确认有无意外文件的 commit
- [ ] 确认 .env 内容是否被 .gitignore 排除:`git check-ignore -v .env`
- [ ] 检查 API 密钥的轮换期限
### 事故发生时的初动
1. 立即使相关 API 密钥失效并轮换
2. 从 git 历史中删除(filter-branch 或 BFG)
3. 确认访问日志以确定受影响范围
4. 向相关人员报告情况
总结
Claude Code 的事故并非 “AI 失控”,绝大多数是**“人类把安全设置拖到以后”**导致的。
| 案例 | 根本原因 | 预防措施 |
|---|---|---|
| .env 泄露 | 无 gitignore | init 脚本 + Hook |
| 生产 DB 删除 | 无环境隔离 | .env 分离 + 确认流程 |
| rm -rf 事故 | 无 deny 列表 | 配置 settings.json |
| 密钥泄露 | 直接写在 prompt 中 | 统一使用环境变量 |
| 计费爆炸 | 无重试上限 | withRetry 工具 |
| force push | 无禁止设置 | deny + force-with-lease |
| 权限过多 | 违反最小权限 | 收紧 IAM 角色 |
今天能做的第一步:只需在 .claude/settings.json 中添加 "deny": ["Bash(rm -rf*)"],就能防止最具破坏性的事故之一。
相关文章
参考资料
免费 PDF:5 分钟看懂 Claude Code 速查表
只需留下邮箱,我们就会立即把这份 A4 一页速查表 PDF 发送给你。
我们会严格保护你的个人信息,绝不发送垃圾邮件。
本文作者
Masa
深度使用 Claude Code 的工程师。运营 claudecode-lab.com——一个涵盖 10 种语言、超过 2,000 页内容的科技媒体。
相关文章
Claude Code 安全最佳实践完全指南:API密钥管理、权限设置与生产环境保护
安全使用 Claude Code 的实战指南。从 API 密钥管理到权限配置、基于 Hooks 的自动化检查,再到生产环境保护——附带可直接运行的代码示例。
Claude Code 权限配置完全指南 | settings.json·Hooks·allowlist 深度解析
全面解析 Claude Code 权限配置。allow/deny/ask 的使用场景、Hooks 自动化、环境专属 settings.json 以及实战配置模式,附完整可运行代码示例。
Harness 工程完全指南|从 Claude Code 学会构建 AI Agent
光靠提示词驾驭不了 LLM。本文用可运行的代码与 Claude Code 的真实架构,手把手拆解串联工具、上下文与控制循环的 harness。