Claude Code Security Best Practices: API Keys, Permissions & Production Protection
A practical security guide for using Claude Code safely. From API key management to permission settings, Hooks-based automation, and production environment protection — with working code examples.
Claude Code has powerful file manipulation and command execution capabilities — but misconfiguration can lead to irreversible accidents. Committing a .env file, accidentally deleting a production database, printing an API key to logs — these are all real incidents caused by using Claude Code without proper safeguards.
This article provides an implementation-level breakdown of security best practices for Claude Code. Rather than theory, the focus is on ready-to-use configuration and preventive code you can copy and apply immediately.
Why Claude Code Needs Security Measures
Unlike a regular text editor, Claude Code has the following capabilities:
- Read, write, and delete any file (
Read/Write/Edit/Bash(rm)) - Execute shell commands (
Bash) - Network access (
WebFetch/ API calls) - Post to external services (Qiita, GitHub, Slack, etc.)
All of these can be executed with user approval. The problem is that mechanically approving every prompt allows unintended operations to slip through. Security measures are about structurally eliminating the room for mistakes.
Measure 1: API Key Management — .env + .gitignore Is the Foundation
What NOT to Do
// ❌ Hardcoded in source code
const client = new Anthropic({ apiKey: "sk-ant-api03-..." });
// ❌ Written in CLAUDE.md or config files
// ANTHROPIC_API_KEY=sk-ant-api03-...
// ❌ Passed inside a claude -p prompt
// Use QIITA_TOKEN=abc123 to post to Qiita
The Right Approach
# .env (excluded from git, stored only on local machine)
ANTHROPIC_API_KEY=sk-ant-api03-...
QIITA_TOKEN=06b4441b...
SLACK_BOT_TOKEN=xoxb-...
DATABASE_URL=postgresql://...
# Always add to .gitignore
.env
.env.*
.env.local
!.env.example # ← Sample file is OK to commit
*.pem
*.key
credentials.json
*-service-account.json
# .env.example (safe to commit, values left empty)
ANTHROPIC_API_KEY=
QIITA_TOKEN=
SLACK_BOT_TOKEN=
DATABASE_URL=
Reading Variables in Code
// ✅ Read from environment variables
import { config } from "dotenv";
config();
const token = process.env.QIITA_TOKEN;
if (!token) throw new Error("QIITA_TOKEN is not set. Please check your .env file.");
Document Prohibitions in CLAUDE.md
## Security Prohibitions
- Never include API keys or tokens in prompts
- Never read and output the contents of .env files
- Never write environment variable values in logs or comments
- Never console.log the contents of process.env
Measure 2: Pre-Commit Secret Scanning
Even with .env in .gitignore, accidental entries in other files or copy-paste mistakes can still happen. Add an automatic pre-commit scan.
Pre-Commit Check with Hooks
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git commit*)",
"hooks": [
{
"type": "command",
"command": "node scripts/secret-scan.mjs"
}
]
}
]
}
}
scripts/secret-scan.mjs:
import { execSync } from "child_process";
// Get staged changes
const diff = execSync("git diff --cached").toString();
const PATTERNS = [
{ name: "Anthropic API Key", re: /sk-ant-api\d+-[A-Za-z0-9_-]{80,}/ },
{ name: "OpenAI API Key", re: /sk-[A-Za-z0-9]{48}/ },
{ name: "AWS Access Key", re: /AKIA[0-9A-Z]{16}/ },
{ name: "Slack Token", re: /xox[baprs]-[0-9A-Za-z-]{10,}/ },
{ name: "Generic Secret", re: /[Ss]ecret[_-]?[Kk]ey\s*[:=]\s*['"][^'"]{10,}['"]/ },
];
const found = PATTERNS.filter(({ re }) => re.test(diff));
if (found.length > 0) {
console.error("🚨 Secret detected! Aborting commit:");
found.forEach(({ name }) => console.error(` - ${name}`));
console.error("\nFix: run `git reset HEAD <file>` to unstage the file");
process.exit(1); // Exit code 1 → Hook blocks the command
}
console.log("✓ Secret scan: no issues found");
process.exit(0);
This means the moment Claude Code tries to run git commit, an automatic scan runs and any detected leaks are blocked.
Measure 3: Permission Mode Configuration
Claude Code’s allow/deny settings can be controlled at a fine-grained level per file.
Permission Settings in .claude/settings.json
{
"permissions": {
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(DROP TABLE*)",
"Bash(truncate*)",
"Bash(curl * | bash)",
"Bash(wget * | sh)"
],
"ask": [
"Write(**)",
"Edit(**)",
"Bash(git commit*)",
"Bash(git push*)",
"Bash(npm publish*)",
"Bash(wrangler pages deploy*)"
]
}
}
| Setting | Meaning |
|---|---|
allow | Execute without confirmation |
deny | Never execute (blocked entirely) |
ask | Requires approval every time |
Key principle: Destructive commands go in deny, write operations in ask, read operations in allow.
Production-Specific Configuration File
For production environments, restricting to read-only is the safest approach.
// .claude/settings.production.json
{
"permissions": {
"allow": ["Read(**)", "Glob(**)", "Grep(**)", "Bash(git log*)", "Bash(git diff*)"],
"deny": ["Write(**)", "Edit(**)", "Bash(git push*)", "Bash(rm*)", "Bash(*deploy*)"],
"ask": []
}
}
# Explicitly specify when working in production
CLAUDE_SETTINGS=.claude/settings.production.json claude
Measure 4: Production Environment Protection
Explicitly Separate Connection Targets
## CLAUDE.md — Production Environment Rules
## Environment Detection
- If DATABASE_URL contains 'prod' or 'production', this is a **production environment**
- In production, never execute:
- DROP / TRUNCATE / DELETE (without WHERE clause)
- Migrations (require prior confirmation)
- Bulk file deletions
## Confirmation Flow
All production changes must:
1. Be tested in a staging environment first
2. Receive user confirmation
3. Report results after execution
Control Connections with Environment Variables
// scripts/db-query.mjs
const env = process.env.NODE_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL;
if (env === "production" && process.argv.includes("--write")) {
console.error("❌ Writing to production requires the --force-production flag");
process.exit(1);
}
Measure 5: File Operation Safety Guards
Automate Backups Before Deletion
// Hooks in .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm *)",
"hooks": [
{
"type": "command",
"command": "echo '⚠️ A delete command is about to run. Press Ctrl+C to cancel.' && sleep 3"
}
]
}
]
}
}
Protect Critical Files from Accidental Edits
## CLAUDE.md — Files That Must Not Be Modified
The following files must **never be edited**:
- .env (contains environment variables and secret keys)
- wrangler.toml (Cloudflare production configuration)
- scripts/deploy.sh (deployment script)
- .github/workflows/*.yml (CI/CD configuration)
If changes are needed, confirm with the user first.
5 Common Pitfalls
1. Adding .gitignore after the fact is too late
A .env file that was already committed remains in git history even after adding it to .gitignore.
# Complete removal from history (caution: requires force push)
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Alternatively, use BFG Repo Cleaner
If the file was already pushed to GitHub, always rotate your API keys first before addressing the history.
2. Storing service account JSON files in the repository
Service account keys for Google Cloud or AWS are often distributed as .json files, but storing them in a repository is dangerous. Convert them to environment variables or migrate to a Secret Manager (AWS Secrets Manager / GCP Secret Manager).
3. Running interactive commands with the Bash tool
During headless execution with claude -p, commands that require interactive input like sudo or vim will cause the process to hang. Use non-interactive commands only.
4. Including credentials in error messages
// ❌ Dangerous: API key appears in logs
throw new Error(`Authentication failed: token=${process.env.TOKEN}`);
// ✅ Safe: don't expose the value
throw new Error(`Authentication failed: please check the TOKEN environment variable`);
5. Reusing the same permission settings across all projects
Using the same settings.json for personal and work projects means the relaxed settings from your personal project can override the stricter requirements needed for work. Manage .claude/settings.json separately for each project.
Security Checklist
A checklist for setting up a Claude Code project:
### Basic Setup
- [ ] Created .env and added to .gitignore
- [ ] Created .env.example and shared with team
- [ ] Verified no secrets in existing commits via git log
### Permission Settings
- [ ] Configured deny list in .claude/settings.json
- [ ] Added destructive commands (rm -rf, DROP TABLE, etc.) to deny
- [ ] Set production deploy commands to ask
### Automation
- [ ] Configured pre-commit secret scan Hook
- [ ] Documented security prohibitions in CLAUDE.md
### Operations
- [ ] Established API key rotation schedule (recommended: every 90 days)
- [ ] Using dedicated settings.production.json for production
- [ ] Documented incident response flow
Summary
Claude Code security isn’t about “imposing restrictions” — it’s about building a structure where accidents can’t happen.
| Threat | Countermeasure |
|---|---|
| API key leakage | .env + .gitignore + secret scan Hook |
| Unintended deletion | deny list + pre-deletion Hook |
| Production mistakes | Production-specific settings + CLAUDE.md prohibitions |
| Commit contamination | PreToolUse Hook for pre-commit scanning |
Once configured, these settings are essentially maintenance-free. Spending 30 minutes today can prevent a major incident in the future.
Related Articles
- Claude Code Permissions Guide
- Claude Code Security Failure Cases
- Claude Code + SaaS Integration Complete Guide
- CLAUDE.md Best Practices
References
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Just enter your email and we'll send you the single-page A4 cheatsheet right away.
We handle your data with care and never send spam.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
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.
Complete Guide to Claude Code Permissions | settings.json, Hooks & Allowlist Explained
A complete guide to Claude Code permissions. Learn allow/deny/ask, automation with Hooks, environment-specific settings.json, and practical patterns—all with working code.
The Complete Guide to Harness Engineering: Building AI Agents the Claude Code Way
Prompts alone can't tame an LLM. Learn how to weave tools, context, and control loops into a harness, with runnable code and Claude Code's own design as a teacher.