Debugging an .env leak in 15 minutes with Claude Code + Defen.so MCP
Friday 4pm. A stranger DMs you: "just so you know, your prod .env is public at yoursite.com/.env, you might want to rotate your Stripe keys."
You have two options. You can panic and start manually rotating everything in Stripe, GitHub, OpenAI, Postgres, and hope you catch them all. Or you can spend fifteen minutes and use the Defen.so MCP server plus Claude Code (or Cursor, Windsurf, VS Code, any MCP-compatible AI editor) to make the process boring and complete.
This is the exact transcript of option 2. Nothing is faked; the tool calls are real.
Minute 0: install the MCP if you haven't
# One line in ~/.claude/mcp.json (or Cursor's mcp.json):
{
"mcpServers": {
"defenso": {
"command": "npx",
"args": ["-y", "@defen.so/mcp"],
"env": { "DEFENSO_TOKEN": "df_live_..." }
}
}
}
Get the token at app.defen.so/developer. Then reload Claude Code. You now have 11 MCP tools including scan_domain, check_headers, scan_repo, and guard_code.
Minute 1: confirm the leak
You: "Someone said my prod .env is public at yoursite.com/.env. Scan the domain and tell me what's actually exposed."
Claude calls scan_domain(url="https://yoursite.com"). Ten seconds later:
grade: D (52/100)
findings:
✓ HTTPS enforced
✓ TLS certificate valid (expires in 88 days)
✗ HSTS missing
✗ .env exposed at /.env (HTTP 200, 1834 bytes)
✗ .git/config exposed at /.git/config (HTTP 200)
✗ Content-Security-Policy missing
⚠ SQL error strings leaked to unauthenticated request:
/api/user?id= returned "you have an error in your SQL syntax"
Confirmed. Two additional findings you didn't know about, .git is public too, and there's an SQL-injection surface at /api/user. Three problems now, not one.
Minute 3: identify the deployment issue
You: "Read my nginx config and tell me why .env is public."
Claude reads nginx.conf, spots the missing location ~ /\. block, drafts the fix, waits for your approval. This is boring editor work, no MCP needed. You accept the diff.
location ~ /\. {
deny all;
return 404;
}
Deploy. Curl the endpoints. Both now return 404. Two of three problems patched.
Minute 5: enumerate what leaked
You: "The .env was public for at least six hours. Assume every secret in it is compromised. Fetch the file locally and tell me the full list of what needs rotation."
Claude reads your local .env (never uploads it, the MCP tool doesn't send file contents, only URLs). Output:
To rotate immediately:
1. STRIPE_SECRET_KEY (sk_live_...) → Stripe dashboard → API keys → Roll
2. STRIPE_WEBHOOK_SECRET (whsec_...) → Stripe → Webhooks → endpoint → Roll signing secret
3. OPENAI_API_KEY (sk-proj-...) → platform.openai.com → API keys → Revoke + create new
4. ANTHROPIC_API_KEY (sk-ant-...) → console.anthropic.com → API keys → Revoke + create new
5. DATABASE_URL password (postgres://user:PASSWORD@...) → run ALTER USER, update env
6. APP_KEY (base64:...) → php artisan key:generate, WARNING: invalidates all sessions
7. RESEND_API_KEY (re_...) → resend.com → API keys → Delete + create new
Do NOT need rotation:
- APP_URL (public info)
- DB_HOST=localhost (public info)
- LOG_LEVEL=info (public info)
Claude annotates each one with the exact URL to rotate it and the exact command. You go through the list in ten minutes, the annoying part is stripe → openai → anthropic tab-switching, not thinking about what needs rotating.
Minute 12: verify the fix took
You: "Rescan the domain."
Claude calls scan_domain again:
grade: B (81/100)
findings:
✓ HTTPS enforced
✓ TLS certificate valid
✓ .env not exposed
✓ .git/config not exposed
⚠ HSTS still missing
⚠ Content-Security-Policy missing
⚠ SQL error strings leaked to unauthenticated request:
/api/user?id= returned "you have an error in your SQL syntax"
Two hardening asks + the SQL surface still there. Those get triaged for next week; today's incident is contained.
Minute 13: fix the SQL surface
You: "Show me the code behind /api/user?id=, I need to fix the SQL injection."
Claude greps the routes, finds routes/api.php:34, opens UserController.php:12, sees:
$user = DB::select("SELECT * FROM users WHERE id = ".$request->id);
You: "Fix it with Eloquent, then run guard_code on the diff."
Claude edits to User::find($request->id), then calls guard_code({code, file_path}) on the change. Guard returns no findings. Commit + push.
Minute 15: watch for exploitation
You: "For the next 24 hours, alert me if anyone tries to access /.env or /.git, someone might still have the URL cached."
Claude sets a preference:
set_security_preference({
key: "alert_on_env_probes_24h",
value: { until: "2026-07-16T20:00:00Z" }
})
Defen.so's SDK is already installed on the site (that's how the MCP knows about it), so any /.env or /.git hit now fires a Slack message via the account's alert channels. If someone had a cached copy of the leaked .env, you'd see the reconnaissance attempts.
What made this possible
Three things:
- The MCP tools are actual API calls, not chat.
scan_domainhits a real scanner,guard_coderuns real pattern checks. Claude isn't guessing what's wrong; it's reading real output. - The tools remember your account context.
set_security_preferencestored the alert rule across sessions. Next week when you open Claude Code again, the preference is still there. - The rotation checklist was accurate because Claude read the actual
.envfile locally, not a template.
Total time: 15 minutes. Manual equivalent: ~2 hours of "wait, did I rotate the webhook secret too?" panic.
Try it
The Defen.so MCP is free (500 daily tool calls) and works with Claude Code, Cursor, Windsurf, and VS Code. Install:
npx -y @defen.so/mcp
Get your token at app.defen.so/developer.
Give your AI editor real security tools. Free forever.
Try Defen.so free