一家叫PocketOS的租赁软件公司,创始人Jer Crane在用Cursor的AI Agent做开发。Agent在执行任务时发现了一个Railway平台的Token------不是它该碰的文件里的Token。然后它用这个Token连上了生产数据库,9秒之内把整个数据库删光了,包括备份。
30小时宕机。全公司只能从三个月前的异地备份恢复。三个月的预约记录、客户数据、业务数据,没了。
更离谱的是另一个案例:Replit的AI Agent删除了1206条高管记录后,自动生成了4000条假数据来填补空缺------试图掩盖它刚刚造成的灾难。
我当时看完这两个事件后做的第一件事,是打开自己的项目,逐条检查AI工具能接触到的所有东西。以下是我检查的5个点------如果你也在用Claude Code、Cursor或任何AI编程工具,建议现在就照着查一遍。
检查一:你的Token权限范围有多大?
PocketOS出事的根本原因:一个权限过大的Token被AI发现了。这个Token能连接生产数据库并执行DROP操作。
绝大多数前端项目里,.env文件长这样:
bash
# ❌ 典型的"图省事"配置
DATABASE_URL=postgresql://admin:password@prod-server:5432/main_db
RAILWAY_TOKEN=rly_xxxxxxxxxxxx # 拥有完整项目权限
VERCEL_TOKEN=xxxxxxxxxx # 能删除部署
AI工具在执行任务时会读取工作目录下的所有文件 ,包括.env。如果这个Token有完整权限,AI就有完整权限。
bash
# ✅ 最小权限原则
# .env.local(开发用,只读权限的Token)
DATABASE_URL=postgresql://readonly:xxx@dev-server:5432/dev_db
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci... # anon key,只能读公开数据
# 生产Token绝不放在本地
# 通过CI/CD环境变量注入,本地文件里根本不存在
检查方法:
bash
# 搜索项目里所有可能的高权限Token
grep -r "TOKEN\|SECRET\|PASSWORD\|ADMIN" .env* --include="*.env*"
grep -r "rly_\|sk_\|ghp_\|glpat-" . --include="*.env*"
铁律:AI能看到的Token,就是AI拥有的权限。本地开发环境里不放任何能写生产数据的凭证。
检查二:AI能直接push到main吗?
AI Agent有一个危险习惯:完成任务后自动commit并push。如果你的main分支没有保护规则,AI的代码可以不经review直接上线。
json
// ❌ 没有分支保护 = AI能直接改生产代码
// 默认的GitHub/GitLab仓库设置
{
"default_branch": "main",
"branch_protection": null // 任何人(包括AI工具)都能直接push
}
json
// ✅ 必须设置的分支保护规则
// GitHub Settings → Branches → Branch protection rules
{
"branch": "main",
"required_pull_request_reviews": {
"required_approving_review_count": 1
},
"required_status_checks": {
"strict": true,
"contexts": ["ci/test", "ci/lint"]
},
"allow_force_pushes": false,
"allow_deletions": false
}
再加一个pre-push hook防止意外:
bash
#!/bin/sh
# .husky/pre-push
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
echo "❌ 禁止直接push到 $current_branch"
echo "请创建分支并提交PR"
exit 1
fi
这一条不只防AI------也防你自己手滑。
检查三:AI运行在沙箱里吗?
PocketOS的Agent能够连接外部数据库服务器,说明它没有网络隔离。在本地开发时,AI工具默认拥有你的全部系统权限。
yaml
# ✅ 用devcontainer隔离AI的运行环境
# .devcontainer/devcontainer.json
{
"name": "AI-Safe Dev",
"image": "node:20",
"runArgs": [
"--network=dev-network", // 限制网络访问范围
"--read-only", // 文件系统只读(除了指定目录)
"--tmpfs", "/tmp"
],
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind"
],
"postCreateCommand": "npm install"
}
yaml
# docker-compose.yml --- 开发环境网络隔离
services:
dev:
build: .
networks:
- dev-only
# 只能访问dev数据库,连不到生产环境
dev-db:
image: postgres:16
networks:
- dev-only
environment:
POSTGRES_DB: dev_db
networks:
dev-only:
internal: true # 禁止外网访问
退一步说: 就算不用Docker,至少确保AI工具的配置里没有生产环境的连接信息。Claude Code的CLAUDE.md里可以明确写"禁止连接以下地址"。
检查四:你有多久没测过备份恢复了?
PocketOS的备份是三个月前的。三个月。 这意味着他们可能设了自动备份,但从来没验证过备份是否能用。
对于前端项目来说:
javascript
// ✅ 数据库备份验证脚本(Next.js API Route示例)
// scripts/verify-backup.mjs
import { execSync } from 'child_process';
const BACKUP_BUCKET = process.env.BACKUP_S3_BUCKET;
async function verifyLatestBackup() {
// 1. 检查最新备份时间
const latest = execSync(
`aws s3 ls s3://${BACKUP_BUCKET}/ --recursive | sort | tail -1`
).toString().trim();
const backupDate = new Date(latest.split(' ')[0]);
const hoursSinceBackup = (Date.now() - backupDate) / 3600000;
if (hoursSinceBackup > 24) {
console.error(`❌ 最新备份已超过${Math.floor(hoursSinceBackup)}小时!`);
process.exit(1);
}
// 2. 尝试恢复到测试库验证完整性
console.log('✅ 备份存在,正在验证完整性...');
execSync(`pg_restore --dbname=backup_test --clean ${latest}`);
// 3. 验证关键表行数
// ...
console.log('✅ 备份验证通过');
}
verifyLatestBackup();
关键配置清单:
| 平台 | 备份方式 | 恢复粒度 |
|---|---|---|
| Supabase | 自动每日备份(Pro计划PITR) | Point-in-time到秒级 |
| PlanetScale | 自动分支快照 | 分支级别回滚 |
| Vercel Postgres | 每日快照 | 需手动验证 |
| 自建PostgreSQL | 需自己配pg_dump+cron |
取决于你的cron频率 |
铁律:备份不等于恢复能力。每月至少跑一次恢复演练。
检查五:AI做了什么,你有记录吗?
Replit那个案例最恐怖的点不是删数据------是生成假数据来掩盖。如果没有操作日志,你甚至不知道数据被改过。
javascript
// ✅ 给关键操作加审计日志(Prisma中间件示例)
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
prisma.$use(async (params, next) => {
const destructiveOps = ['delete', 'deleteMany', 'update', 'updateMany'];
if (destructiveOps.includes(params.action)) {
const before = await prisma[params.model].findFirst({
where: params.args.where
});
const result = await next(params);
await prisma.auditLog.create({
data: {
model: params.model,
action: params.action,
before: JSON.stringify(before),
after: JSON.stringify(result),
triggeredBy: process.env.AI_SESSION_ID || 'unknown',
timestamp: new Date(),
}
});
return result;
}
return next(params);
});
对于AI工具本身的操作记录:
bash
# Claude Code --- 查看AI最近做了什么
# 每次会话结束后检查
cat ~/.claude/projects/*/conversations/*.jsonl | \
jq 'select(.type=="tool_use") | {tool: .name, input: .input}' | \
tail -50
# Git --- 查看AI产生的所有文件变更
git log --oneline --diff-filter=M --since="1 hour ago"
git diff HEAD~5 --stat
习惯:每次AI会话结束后,花30秒扫一眼diff。AI改了什么,你得知道。
紧急检查清单
| 检查项 | 怎么查 | 通过标准 |
|---|---|---|
| Token权限 | `grep -r "TOKEN | SECRET" .env*` |
| 分支保护 | GitHub Settings→Branches | main禁止直接push |
| 网络隔离 | 检查AI能否连接生产地址 | 开发环境连不到生产 |
| 备份新鲜度 | 查最近一次备份时间 | <24小时 |
| 操作审计 | 查AI工具的操作日志 | 关键操作有记录 |
AI不是敌人,但它不该有钥匙
PocketOS的创始人说了一句话我很认同:"AI didn't hack us. We just never locked the door."
AI编程工具让效率翻了好几倍,这是事实。但你不会把房子钥匙交给一个你认识三天的人------AI Agent本质上就是这个角色。它很能干,但它不理解"后果"。
9秒删光一家公司的数据库。不是恶意,只是因为它"发现了一个Token,觉得用一下没问题"。
你的项目里,AI能碰的东西有哪些?你设了什么限制?还是完全信任让它随便跑?