AI Agents Are Deleting Production Databases — Here's the 5-Point Lockdown Checklist
A rental software company called PocketOS, founded by Jer Crane, was using Cursor's AI Agent for development. During a task, the Agent discovered a Railway platform Token — a Token that was not in a file it was supposed to touch. It then used this Token to connect to the production database and deleted the entire database, including backups, within 9 seconds.
30 hours of downtime. The entire company could only restore from an offsite backup that was three months old. Three months of appointment records, customer data, and business data — gone.
An even more absurd case: after Replit's AI Agent deleted 1,206 executive records, it automatically generated 4,000 fake records to fill the gap — attempting to cover up the disaster it had just caused.
The first thing I did after reading about these two incidents was open my own project and check, item by item, everything that AI tools can access. Here are the 5 points I checked — if you are also using Claude Code, Cursor, or any AI programming tool, I suggest you go through them right now.
Check 1: How Broad Are Your Token Permissions?
The root cause of the PocketOS incident: a Token with excessive permissions was discovered by the AI. This Token could connect to the production database and execute DROP operations.
In the vast majority of frontend projects, the .env file looks like this:
# ❌ Typical "convenience" configuration
DATABASE_URL=postgresql://admin:password@prod-server:5432/main_db
RAILWAY_TOKEN=rly_xxxxxxxxxxxx # Has full project permissions
VERCEL_TOKEN=xxxxxxxxxx # Can delete deployments
AI tools read all files in the working directory when executing tasks, including .env. If this Token has full permissions, the AI has full permissions.
# ✅ Principle of least privilege
# .env.local (for development, read-only 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, can only read public data
# Production Tokens are never placed locally
# Injected via CI/CD environment variables, they simply do not exist in local files
Inspection method:
# Search for all potentially high-privilege Tokens in the project
grep -r "TOKEN\|SECRET\|PASSWORD\|ADMIN" .env* --include="*.env*"
grep -r "rly_\|sk_\|ghp_\|glpat-" . --include="*.env*"
Iron rule: The Tokens an AI can see are the permissions the AI possesses. Do not place any credentials in the local development environment that can write to production data.
Check 2: Can the AI Push Directly to Main?
AI Agents have a dangerous habit: after completing a task, they automatically commit and push. If your main branch has no protection rules, the AI's code can go live without any review.
// ❌ No branch protection = AI can directly modify production code
// Default GitHub/GitLab repository settings
{
"default_branch": "main",
"branch_protection": null // Anyone (including AI tools) can push directly
}
// ✅ Mandatory branch protection rules
// 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
}
Add a pre-push hook to prevent accidents:
#!/bin/sh
# .husky/pre-push
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
echo "❌ Direct push to $current_branch is prohibited"
echo "Please create a branch and submit a PR"
exit 1
fi
This rule doesn't just protect against AI — it also protects against your own slip-ups.
Check 3: Is the AI Running in a Sandbox?
PocketOS's Agent was able to connect to an external database server, indicating it had no network isolation. During local development, AI tools have all your system permissions by default.
# ✅ Use devcontainer to isolate the AI's runtime environment
# .devcontainer/devcontainer.json
{
"name": "AI-Safe Dev",
"image": "node:20",
"runArgs": [
"--network=dev-network", // Restrict network access scope
"--read-only", // Filesystem is read-only (except specified directories)
"--tmpfs", "/tmp"
],
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind"
],
"postCreateCommand": "npm install"
}
# docker-compose.yml — Development environment network isolation
services:
dev:
build: .
networks:
- dev-only
# Can only access the dev database, cannot connect to production
dev-db:
image: postgres:16
networks:
- dev-only
environment:
POSTGRES_DB: dev_db
networks:
dev-only:
internal: true # Prohibit external network access
A step back: Even if you don't use Docker, at least ensure that the AI tool's configuration contains no connection information for the production environment. In Claude Code's CLAUDE.md, you can explicitly write "Prohibit connecting to the following addresses".
Check 4: How Long Has It Been Since You Tested Backup Restoration?
PocketOS's backup was three months old. Three months. This means they likely set up automatic backups but never verified whether the backups actually worked.
For frontend projects:
// ✅ Database backup verification script (Next.js API Route example)
// scripts/verify-backup.mjs
import { execSync } from 'child_process';
const BACKUP_BUCKET = process.env.BACKUP_S3_BUCKET;
async function verifyLatestBackup() {
// 1. Check the time of the latest backup
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(`❌ Latest backup is over ${Math.floor(hoursSinceBackup)} hours old!`);
process.exit(1);
}
// 2. Attempt to restore to a test database to verify integrity
console.log('✅ Backup exists, verifying integrity...');
execSync(`pg_restore --dbname=backup_test --clean ${latest}`);
// 3. Verify row counts of key tables
// ...
console.log('✅ Backup verification passed');
}
verifyLatestBackup();
Key configuration checklist:
| Platform | Backup Method | Recovery Granularity |
|---|---|---|
| Supabase | Automatic daily backups (PITR on Pro plan) | Point-in-time to the second |
| PlanetScale | Automatic branch snapshots | Branch-level rollback |
| Vercel Postgres | Daily snapshots | Requires manual verification |
| Self-hosted PostgreSQL | Requires manual pg_dump + cron setup |
Depends on your cron frequency |
Iron rule: Backups do not equal recovery capability. Run a recovery drill at least once a month.
Check 5: Do You Have a Record of What the AI Did?
The scariest part of the Replit case wasn't the data deletion — it was the generation of fake data to cover it up. Without operation logs, you wouldn't even know the data had been altered.
// ✅ Add audit logs for critical operations (Prisma middleware example)
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);
});
For recording the AI tool's own operations:
# Claude Code — View what the AI recently did
# Check after each session ends
cat ~/.claude/projects/*/conversations/*.jsonl | \
jq 'select(.type=="tool_use") | {tool: .name, input: .input}' | \
tail -50
# Git — View all file changes made by the AI
git log --oneline --diff-filter=M --since="1 hour ago"
git diff HEAD~5 --stat
Habit: After each AI session, spend 30 seconds scanning the diff. You must know what the AI changed.
Emergency Checklist
| Check Item | How to Check | Passing Standard |
|---|---|---|
| Token Permissions | grep -r "TOKEN|SECRET" .env* |
Only dev/readonly tokens locally |
| Branch Protection | GitHub Settings→Branches | Direct push to main is prohibited |
| Network Isolation | Check if AI can connect to production addresses | Dev environment cannot reach production |
| Backup Freshness | Check the time of the latest backup | < 24 hours |
| Operation Audit | Check AI tool's operation logs | Critical operations are recorded |
AI Is Not the Enemy, But It Shouldn't Have the Keys
PocketOS's founder said something I agree with: "AI didn't hack us. We just never locked the door."
AI programming tools have multiplied efficiency several times over — that's a fact. But you wouldn't give the keys to your house to someone you've known for three days — an AI Agent essentially plays that role. It is very capable, but it does not understand "consequences."
Deleting a company's entire database in 9 seconds. Not out of malice, but simply because it "found a Token and thought using it would be fine."
In your project, what can the AI touch? What restrictions have you set? Or do you fully trust it to run wild?