Tips & Tricks

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 泄露无 gitignoreinit 脚本 + Hook
生产 DB 删除无环境隔离.env 分离 + 确认流程
rm -rf 事故无 deny 列表配置 settings.json
密钥泄露直接写在 prompt 中统一使用环境变量
计费爆炸无重试上限withRetry 工具
force push无禁止设置deny + force-with-lease
权限过多违反最小权限收紧 IAM 角色

今天能做的第一步:只需在 .claude/settings.json 中添加 "deny": ["Bash(rm -rf*)"],就能防止最具破坏性的事故之一。

相关文章

参考资料

#claude-code #security #incident #best-practices #devops

让你的 Claude Code 工作流更上一层楼

50 个经过实战检验的提示词模板,现在就能复制粘贴到 Claude Code 中使用。

免费

免费 PDF:5 分钟看懂 Claude Code 速查表

只需留下邮箱,我们就会立即把这份 A4 一页速查表 PDF 发送给你。

我们会严格保护你的个人信息,绝不发送垃圾邮件。

Masa

本文作者

Masa

深度使用 Claude Code 的工程师。运营 claudecode-lab.com——一个涵盖 10 种语言、超过 2,000 页内容的科技媒体。