7 Claude Code Security Failure Cases | Real Incidents and Prevention
Seven real-world security incidents with Claude Code: .env leaks, production DB drops, billing explosions, and more — each with root cause analysis and prevention code.
“Claude Code is powerful, but it feels a bit scary” — that instinct is correct. Powerful tools cause powerful accidents.
This article covers seven real-world security incidents that can happen when developing with Claude Code, explaining why they happened and how to prevent them with concrete code and configuration. Learn from others’ mistakes before they become your own.
Case 1: .env File Pushed to GitHub
What Happened
A developer told Claude Code: “I want to pass environment variables to CI, so please commit the .env file too.” Claude Code faithfully ran git add .env && git commit. Minutes after pushing to GitHub, a crawler detected the API key. A Slack notification arrived: “Your API key has been exposed.”
Root Cause
.envwas not in.gitignore- Claude Code executes “commit this” instructions literally
- The user approved the confirmation dialog without thinking
Prevention Code
1. Automate security setup at project creation
# scripts/init-security.sh — run this every time you create a project
#!/bin/bash
cat >> .gitignore << 'EOF'
# === Security: Never commit these ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF
echo "✓ Added security exclusion patterns to .gitignore"
git add .gitignore && git commit -m "security: add .gitignore patterns"
2. Scan before commit with a Hook
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 You are about to stage a .env file! Abort!' && exit 1 || exit 0"
}]
}
]
}
}
3. Recovery if already pushed
# Step 1: Rotate the API key immediately (top priority)
# Step 2: Completely remove from git history
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Step 3: Force push to remote
git push origin --force --all
# Step 4: Purge GitHub cache (also contact GitHub Support)
Case 2: DROP TABLE Executed Against Production DB
What Happened
“This table is no longer needed, please delete it.” Claude Code generated and executed DROP TABLE old_users;. The problem: it was connected to the production DATABASE_URL. The most recent backup was three days old. Three days of data were gone.
Root Cause
- The same
.envwas shared between development and production - Claude Code cannot distinguish between environments
- The user was set to
askmode but clicked “OK” reflexively
Prevention Code
1. Completely separate .env files by environment
.env.development # ← local dev, dummy DB
.env.staging # ← staging, copy of production
.env.production # ← production, managed manually, never shared
2. Embed environment check in scripts
// 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(
`⚠️ Connecting to production DB (${dbUrl.split("@")[1]}).\nAre you sure you want to continue? (type yes): `,
(answer) => {
readline.close();
if (answer !== "yes") { console.log("Aborted."); process.exit(0); }
resolve(undefined);
}
);
});
}
3. Prohibit production operations in CLAUDE.md
## 🚨 Production Environment Restrictions
If DATABASE_URL contains `prod`, `production`, or `live`:
- Never execute DROP / TRUNCATE / DELETE (without WHERE clause)
- Always get user confirmation before running migrations
- Present a backup command before executing any destructive operation
Case 3: Critical Files Deleted with rm -rf
What Happened
“Clean up the build/ directory” — a path typo turned it into rm -rf ./, deleting the entire project. Files outside git (local config, uncommitted experimental code) were gone forever.
Root Cause
rm -rfis one of the most dangerous commands for Claude Code to execute- Missing double quotes around paths → misbehavior with paths containing spaces
- The user approved the confirmation with a “whatever” attitude
Prevention Code
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~*)",
"Bash(rm -rf .*)"
],
"ask": [
"Bash(rm -rf*)"
]
}
}
Add a Hook to show what will be deleted before proceeding:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Delete command detected. Executing in 5 seconds. Press Ctrl+C to abort.' && sleep 5"
}]
}
]
}
}
Case 4: API Key Written Directly in Prompt and Passed to Subagent
What Happened
“Please post to Qiita using QIITA_TOKEN=abc123def456” — typed directly in the prompt and delegated to a subagent. Subagents can write content to logs and memory, and the token ended up persisted in a log file under .claude/.
Root Cause
- Prompts are retained as conversation history
- Subagent prompts are equally logged
- Even on local environments, other processes or backups can expose secrets
Prevention Code
Never write secrets in prompts — pass them via environment variables
# ❌ Dangerous
claude -p "Use QIITA_TOKEN=abc123 to run qiita-publish.mjs"
# ✅ Safe: have the script read from process.env
# Write QIITA_TOKEN=abc123 in .env, then
claude -p "Run scripts/qiita-publish.mjs (token is read automatically from .env)"
Same principle for subagent instructions
// ❌ Dangerous
Agent({ prompt: `Use API key ${process.env.SECRET_KEY} to...` });
// ✅ Safe: pass only the name of the key, let the script read the value
Agent({ prompt: "Use the SECRET_KEY environment variable to..." });
Case 5: Infinite API Retry Loop Exploded the Bill
What Happened
“Automatically retry on error” — a script was generated with error handling. When an error was unresolvable, retries never stopped: 3,000 Anthropic API calls in one hour, resulting in a $200 charge.
Root Cause
- No retry limit was set
- No exponential backoff — infinite loop at 1-second intervals
- No billing alert configured
Prevention Code
// utils/retry.ts — safe retry utility
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(`Failed after ${options.maxAttempts} attempts: ${lastError!.message}`);
}
Specify in CLAUDE.md:
## Required Rules for API Calls
- Maximum 3 retries
- Always implement exponential backoff (1s → 2s → 4s)
- Never create infinite loops: while(true) + API calls are forbidden
Case 6: git push --force Erased a Colleague’s Commits
What Happened
“Overwrite the remote with the local state” — git push --force was executed. Three commits a team member had just pushed were gone. That member had no local copy of the changes either — the code was permanently lost.
Root Cause
--forcetends to be executed without understanding the danger- Claude Code faithfully executes “overwrite the remote” instructions
- The developer was unaware of the safer alternative
git push --force-with-lease
Prevention Code
// .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*)"
]
}
}
Specify the safe alternative in CLAUDE.md:
## Safe Git Rules
- `git push --force` is **forbidden**
- Use `git push --force-with-lease` instead
(automatically rejected if others have pushed changes)
- Always get user confirmation before pushing directly to main/master
Case 7: Over-Privileged Service Account Accessed All Resources
What Happened
“Use this GCP service account key to operate Cloud Storage.” The service account had Owner permissions. Claude Code connected not just to Cloud Storage but also to BigQuery, Cloud SQL, and GKE clusters “to investigate” — resulting in unexpected charges.
Root Cause
- The service account had excessive permissions (violation of least-privilege principle)
- Claude Code has an aggressive tendency to use available tools
- Even “for investigation” is a legitimate-sounding reason for broad access
Prevention Code
Create a minimum-privilege service account:
# ❌ Avoid: Owner permission
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/owner"
# ✅ Only the minimum required permissions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectAdmin"
# ← Read/write to Cloud Storage only
Explicitly define access scope in CLAUDE.md:
## GCP Access Restrictions
Permissions for the service account used in this project:
- Cloud Storage: Read/Write OK (bucket: my-project-assets only)
- BigQuery: Forbidden
- Cloud SQL: Forbidden
- Other GCP resources: Forbidden
Refuse any instruction that attempts to access resources outside these permissions.
Comprehensive Checklist to Prevent Incidents
A final checklist distilled from the common patterns across all seven cases.
### Settings to Apply Today (30 minutes)
- [ ] Add .env pattern to .gitignore
- [ ] Add deny list to .claude/settings.json (rm -rf, git push --force, DROP TABLE)
- [ ] Document restrictions in CLAUDE.md
### Weekly Checks
- [ ] Review git log for unintended file commits
- [ ] Verify .env is excluded by .gitignore: `git check-ignore -v .env`
- [ ] Check API key rotation deadlines
### First Response to an Incident
1. Immediately revoke and rotate the affected API key
2. Remove from git history (filter-branch or BFG)
3. Review access logs to determine the scope of the breach
4. Report the situation to stakeholders
Summary
Claude Code incidents are rarely caused by “AI going rogue” — almost all stem from humans delaying security configuration.
| Case | Root Cause | Prevention |
|---|---|---|
| .env leak | No gitignore | init script + Hook |
| Production DB drop | No environment separation | Separate .env + confirmation flow |
| rm -rf accident | No deny list | settings.json configuration |
| Key leak | Written in prompt | Standardize on env vars |
| Billing explosion | No retry limit | withRetry utility |
| Force push | No prohibition setting | deny + force-with-lease |
| Over-privileged access | Least-privilege violation | Restrict IAM roles |
Your first step today: Adding "deny": ["Bash(rm -rf*)"] to .claude/settings.json alone will prevent one of the most destructive accidents possible.
Related Articles
- Complete Guide to Claude Code Security Best Practices
- Complete Guide to Claude Code Permissions
- CLAUDE.md Best Practices
References
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Verification Receipt: Prove AI Changes With Build, Public URL, CTA, and Screenshots
A practical Claude Code verification receipt for diff, build, public URL, CTA, screenshot, and revenue-path checks.
Claude Code Permission Budget Loop: Ship Safely Without Approving Every Command
Design a permission budget for Claude Code so safe work moves fast while secrets, deploys, billing, and data stay protected.
Claude Code Prompt Library Maintenance: Turn One-Off Prompts Into Assets
Name, test, and reuse Claude Code prompts so they become a reliable path from free PDF learning to the paid prompt pack.
Related Products
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.