Tips & Tricks (Updated: 6/1/2026)

Claude Code Security Best Practices: API Keys, Permissions & Production Protection

Practical Claude Code security for API keys, permissions, secrets, commit scans, and production protection with working examples.

Claude Code Security Best Practices: API Keys, Permissions & Production Protection

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: "PASTE_REAL_API_KEY_HERE" });

// ❌ Written in CLAUDE.md or config files
// ANTHROPIC_API_KEY=PASTE_REAL_API_KEY_HERE

// ❌ Passed inside a claude -p prompt
// Use QIITA_TOKEN=PASTE_REAL_TOKEN_HERE to post to Qiita

The Right Approach

# .env (excluded from git, stored only on local machine)
ANTHROPIC_API_KEY=<anthropic-api-key>
QIITA_TOKEN=<qiita-token>
SLACK_BOT_TOKEN=<slack-bot-token>
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 assignment", re: /\b(?:ANTHROPIC_API_KEY|CLAUDE_API_KEY)\s*[:=]\s*["']?[^"'\s]{20,}/i },
  { name: "OpenAI API key assignment", re: /\bOPENAI_API_KEY\s*[:=]\s*["']?[^"'\s]{20,}/i },
  { name: "AWS Access Key", re: /\bAKIA[0-9A-Z]{16}\b/ },
  { name: "Slack Token", re: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/ },
  { name: "Generic Secret", re: /\b(?:secret|token|api[_-]?key|password)\s*[:=]\s*["'][^"']{10,}["']/i },
];

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

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable",
    "allow": [
      "Read(**)",
      "Glob(**)",
      "Grep(**)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "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*)"
    ]
  },
  "disableAutoMode": "disable"
}
SettingMeaning
allowExecute without confirmation
denyNever execute (blocked entirely)
askRequires approval every time

Key principle: Destructive commands go in deny, write operations in ask, read operations in allow.

Separate Production and Organization Policy with Local or Managed Settings

The official settings model applies scopes in this order: managed, command-line arguments, local, project, then user. Instead of relying on an unofficial custom-file switch, use .claude/settings.local.json for a stricter personal production terminal and managed settings for organization-wide rules.

Use .claude/settings.local.json for local-only overrides and keep it out of git.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "plan",
    "disableBypassPermissionsMode": "disable",
    "allow": ["Read(**)", "Glob(**)", "Grep(**)", "Bash(git log*)", "Bash(git diff*)"],
    "deny": ["Write(**)", "Edit(**)", "Bash(git push*)", "Bash(rm*)", "Bash(*deploy*)"],
    "ask": []
  },
  "disableAutoMode": "disable"
}

For a team or company policy, put the same boundary in managed settings. With allowManagedPermissionRulesOnly, users and project repositories cannot redefine allow, ask, or deny rules around the organization policy.

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(curl *)"
    ],
    "disableBypassPermissionsMode": "disable"
  },
  "disableAutoMode": "disable",
  "allowManagedPermissionRulesOnly": true
}

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.

Practical Operating Rules

Do Not Expose Secrets During Normal Work

When you ask Claude Code to “fix this error”, do not paste the full log or the contents of .env. Decide up front that API keys, OAuth tokens, cookies, database URLs, customer emails, billing IDs, and private keys are never copied into prompts or issue comments.

If Claude Code needs context, redact values and keep only the shape of the configuration. DATABASE_URL=***REDACTED*** is enough for Claude Code to understand that a database URL exists. The real value is not needed for debugging most application code.

Safe to ProvideDo Not Provide
Error type, stack trace file names, reproduction stepsAPI keys, private keys, session cookies, real .env values
.env.example, setting names, permission rules, failed command nameProduction database URLs, customer data, internal credentials
Redacted logs, test data, local sample valuesReal tokens, service account JSON, screenshots containing secrets

Put this table in CLAUDE.md so every session starts with the same boundary. In teams, “do not expose secrets” should be an operating rule, not tribal knowledge.

Duplicate Secret Scanning Before Commit

The PreToolUse Hook above blocks a commit when Claude Code is the actor. It does not cover a human committing from another terminal or generated files created in CI. In production teams, layer three checks: the Claude Code Hook, a local pre-commit scan, and a CI secret scan that blocks merge.

The minimum useful setup is: block locally with the Claude Code Hook, re-check in CI, and fail the pull request when a secret pattern appears. Tools such as gitleaks or trufflehog are better for CI-scale detection, while the script in this article remains useful as a fast local guard.

Concrete Failure Examples

Failure 1: Reading .env during investigation A developer asked Claude Code to “check the environment variables”, and real values ended up in conversation or working logs. The fix is to deny reads of .env and provide .env.example only.

Failure 2: Pasting a Qiita publishing token into a prompt An automation task included QIITA_TOKEN directly in the prompt, leaving a chance that a subagent or log file would persist it. The safer workflow is to keep the token in .env and have commands reference only the environment variable name.

Failure 3: Production and staging database URLs looked similar The instruction said only “clean up the database” and skipped connection verification. If the production URL was active, a delete or migration could become an incident. Print NODE_ENV, host, and database name before write operations, and require explicit user confirmation.

First Response After an Incident

If you suspect a secret leaked, the first step is not history cleanup. The first step is disabling the credential.

  1. Rotate or revoke the affected API key, token, or password immediately
  2. Check GitHub Actions, Cloudflare, AWS, GCP, and SaaS audit logs for use after exposure
  3. Record who exposed what, when, in which repository, and through which channel
  4. Remove the secret from git history and logs, then communicate any force-push impact
  5. Tighten deny rules, Hooks, CI scans, and CLAUDE.md prohibitions before resuming work

In practice, stopping the credential first and cleaning history second reduces mistakes. During incident response, people often paste more logs while trying to help, so prepare a redacted-log template before you need it.

Training and Consultation Path

Claude Code security is not solved by one settings file. You also need to review repository layout, CI, deployment permissions, and team approval flow.

At ClaudeCodeLab, the learning path for individual developers starts with .env hygiene and permission rules. For team consultations, the first questions are where managed settings should be enforced and where CI secret scanning should block merges. If Claude Code is already used at work, start by mapping who can access which secrets before tuning individual permissions.

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)
- [ ] Production work is restricted with .claude/settings.local.json or managed settings
- [ ] Documented incident response flow

Summary

Claude Code security isn’t about “imposing restrictions” — it’s about building a structure where accidents can’t happen.

ThreatCountermeasure
API key leakage.env + .gitignore + secret scan Hook
Unintended deletiondeny list + pre-deletion Hook
Production mistakeslocal/managed settings + CLAUDE.md prohibitions
Commit contaminationPreToolUse 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.

References

#claude-code #security #api-key #permissions #devops #best-practices
Free

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.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.