Security Best Practices

Fully mastering Claude Code's security mechanisms β€” permission control, sensitive file protection, command interception, API key management

Security Best Practices

Claude Code is a powerful AI programming assistant, but "powerful" also means it requires high system permissions β€” reading and writing files, executing commands, accessing the network. This is like giving an intern admin access: capable, but security boundaries are essential.

This document will help you establish a comprehensive security defense, from personal use to team collaboration, from development environments to CI/CD automation, ensuring you enjoy AI programming efficiency without stepping on security pitfalls.

Understanding Claude Code's Security Model

Claude Code Runs in Your Terminal

This is crucial and the foundation for understanding all security measures: Claude Code does not run in a cloud sandbox but directly in your local terminal.

This means:

  • It can read any file on your machine (as long as your user has permission)
  • It can execute any shell command
  • It can access your environment variables (including any secrets they may contain)
  • It can send and receive data over the network

Of course, Claude Code has built-in security mechanisms to constrain these capabilities. But you should always remember this fundamental fact: its maximum permissions are your user permissions.

Permission Approval Mechanism

Claude Code's core security philosophy is Human in the Loop. By default, it requests your confirmation before executing the following operations:

  • First use of a tool
  • Executing shell commands (especially those you haven't pre-approved)
  • Writing new files or modifying existing files
  • Accessing network resources

The permission requests you see look roughly like this:

Claude wants to run: rm -rf node_modules && npm install

Allow?
  (y) Yes, allow once
  (a) Always allow "npm install" commands
  (n) No, deny

Key Principle: If you're unsure whether an operation is safe, choose "No". You can always approve it later once you understand the situation.

Three Lines of Defense

Claude Code's security system consists of three lines of defense:

First Line: Permission Mode (Global Policy)
  ↓
Second Line: Allow/Deny Rules (Fine-grained Control)
  ↓
Third Line: Hooks (Custom Interception Logic)

We'll cover each one below.

Permission Configuration

Comparison of Four Permission Modes

Claude Code provides four permission modes, with security levels from high to low:

Mode File Editing Command Execution Use Cases Security Level
plan Forbidden Forbidden (read-only only) Code review, architecture analysis Highest
default Ask first time Ask every time Daily development High
acceptEdits Auto-approve Ask every time Tasks requiring frequent file modifications Medium
bypassPermissions Auto-approve Auto-approve CI/CD, trusted environments Low

Detailed Explanation of Each Mode:

plan mode: Claude can only search and read code, cannot make any modifications. Suitable for scenarios where you want Claude to analyze code but don't want it to touch anything. Switch to this mode via Shift+Tab.

default mode: The recommended daily mode. Claude will ask the first time it uses a type of tool; after you approve, similar operations won't ask again. Shell commands always require confirmation.

acceptEdits mode: Trust Claude's file editing capabilities but remain vigilant about command execution. Suitable for scenarios where you need Claude to make extensive modifications to code files but don't want it to execute arbitrary commands.

bypassPermissions mode: Skip all permission checks. Only use this when you completely trust the current task or are in an isolated CI/CD environment.

Viewing and Modifying via /permissions

You can type /permissions anytime in Claude Code to view the current permission status:

/permissions

The output will show the current mode, approved tools, Allow/Deny rules, and more.

Permission Configuration in settings.json

Permission configuration is stored in settings.json, divided into two levels:

User-level (affects all projects): ~/.claude/settings.json Project-level (affects only the current project): .claude/settings.json

{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test:*)",
      "Bash(git status)",
      "Bash(git add:*)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(node:*)",
      "Bash(python:*)"
    ],
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)"
    ]
  }
}

Rule Syntax Explanation:

Syntax Meaning Example
Bash(cmd) Exact command match Bash(npm run lint)
Bash(cmd:*) Match command prefix Bash(git log:*) matches all commands starting with git log
Read(path) Match file read Read(.env)
Write(path) Match file write Write(src/**)
** Multi-level directory wildcard Read(secrets/**) matches all files at all levels under secrets
* Single-level wildcard Read(.env.*) matches .env.local, .env.production, etc.

Important: Deny rules have higher priority than Allow rules. Even if you approve an operation in Allow, if it also matches a Deny rule, it will still be rejected.

Sensitive File Protection

Using .claudeignore to Exclude Sensitive Directories

The syntax of the .claudeignore file is the same as .gitignore. Files and directories listed are completely invisible to Claude Code:

Create .claudeignore in the project root:

# Environment variables and keys
.env
.env.*
secrets/
*.pem
*.key
*credentials*

# Sensitive configuration
config/production.yml
config/secrets.yml

# Personal data
data/users/
*.sqlite
*.db

# Other sensitive directories
.aws/
.ssh/
.gnupg/

Note: .claudeignore makes Claude completely unable to see these files β€” it doesn't even know these files exist. This is more thorough than Deny rules.

Using Hooks to Intercept Modifications to Sensitive Files

If you want Claude to be able to read certain files but not modify them, you can use Hooks for fine-grained control:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"\nimport sys, json, os\ntool_input = json.loads(os.environ.get('TOOL_INPUT', '{}'))\npath = tool_input.get('file_path', '') or tool_input.get('path', '')\nprotected = ['.env', 'credentials', 'secret', '.pem', '.key']\nfor p in protected:\n    if p in path.lower():\n        print(f'BLOCKED: Modification of files containing {p} is not allowed', file=sys.stderr)\n        sys.exit(2)\n\""
          }
        ]
      }
    ]
  }
}

This Hook checks the file path before Claude attempts to write or edit a file. If the path contains sensitive keywords, it blocks the operation (exit code 2 means block).

Protection Configuration Template

For team projects, it's recommended to create a standard security configuration file stored in the repository:

// .claude/settings.json (committed to Git)
{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Write(.git/**)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)",
      "Bash(chmod 777:*)",
      "Bash(curl|wget:*)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$TOOL_INPUT\" | python3 -c \"import sys,json; cmd=json.load(sys.stdin).get('command',''); blocked=['DROP TABLE','DELETE FROM','TRUNCATE','password','token','secret']; [sys.exit(2) for b in blocked if b.lower() in cmd.lower()]\""
          }
        ]
      }
    ]
  }
}

Command Execution Security

Understanding Bash Tool Behavior

Claude Code executes shell commands through the Bash tool. By default, besides commands you've pre-approved, each execution requires your confirmation.

Some behaviors to note:

  • Commands execute in your current shell environment
  • Environment variables are visible to commands
  • Commands can modify the filesystem
  • Commands can install software
  • Commands can access the network

Which Commands Are Auto-Approved

When you add a command pattern to the allow list in settings.json, matching commands execute automatically without prompting.

Recommended pre-approved safe commands:

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(git branch:*)",
      "Bash(ls:*)",
      "Bash(cat:*)",
      "Bash(head:*)",
      "Bash(tail:*)",
      "Bash(wc:*)",
      "Bash(find:*)",
      "Bash(grep:*)",
      "Bash(npm run lint:*)",
      "Bash(npm run test:*)",
      "Bash(python -m pytest:*)"
    ]
  }
}

Dangerous Command Blocking

The following commands should always be in the Deny list and never allowed to execute automatically:

{
  "permissions": {
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(rm -r /:*)",
      "Bash(git push --force:*)",
      "Bash(git push -f:*)",
      "Bash(git reset --hard:*)",
      "Bash(git checkout -- .:*)",
      "Bash(git clean -f:*)",
      "Bash(chmod 777:*)",
      "Bash(chown:*)",
      "Bash(dd:*)",
      "Bash(mkfs:*)",
      "Bash(shutdown:*)",
      "Bash(reboot:*)"
    ]
  }
}

Custom Command Blocking via Hooks

For more complex blocking logic, use Hooks:

Example 1: Block all network request commands

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$TOOL_INPUT\" | python3 -c \"\nimport sys, json\ncmd = json.load(sys.stdin).get('command', '')\nnet_cmds = ['curl', 'wget', 'nc ', 'netcat', 'ssh ', 'scp ', 'rsync']\nfor nc in net_cmds:\n    if nc in cmd:\n        print(f'BLOCKED: Network commands are not allowed ({nc})', file=sys.stderr)\n        sys.exit(2)\n\""
          }
        ]
      }
    ]
  }
}

Example 2: Log all commands to audit log

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') [Bash] $TOOL_INPUT\" >> ~/.claude/audit.log"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') [FileChange] $TOOL_INPUT\" >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

This gives you a complete operation audit log that you can refer back to at any time to see what Claude Code did.

API Key Security

Don't Hardcode API Keys in CLAUDE.md

This is the most common security mistake. The CLAUDE.md file is typically committed to the Git repository, and Claude Code reads it every time it starts. If it contains an API Key, then:

  1. The Key will be committed to Git history (even if deleted later)
  2. The Key is visible to Claude Code and may appear in logs or analysis results
  3. Other team members can all see this Key

Wrong approach:

<!-- CLAUDE.md -->
## API Configuration
Database connection: postgresql://admin:P@ssw0rd@localhost:5432/mydb
OpenAI Key: sk-xxxxxxxxxxxxxxxxxxxx

Correct approach:

<!-- CLAUDE.md -->
## API Configuration

- Database connection and API Keys are managed via .env file
- .env file is not committed to Git (already in .gitignore)
- New developers need to copy .env.example and fill in their own credentials

Use Environment Variables for Key Management

Recommended key management approach:

# .env (not committed to Git)
DATABASE_URL=postgresql://admin:P@ssw0rd@localhost:5432/mydb
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
REDIS_URL=redis://localhost:6379

# .env.example (committed to Git, without actual values)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
OPENAI_API_KEY=sk-your-key-here
REDIS_URL=redis://localhost:6379

Ensure .env is in both .gitignore and .claudeignore:

# .gitignore
.env
.env.local
.env.production

# .claudeignore (if you don't want Claude to read .env)
.env
.env.*

Steps to Take After API Key Exposure

If you suspect an API Key has been exposed (for example, accidentally committed to Git), immediately execute the following steps:

1. Rotate the key immediately

   - Go to the corresponding service's console to generate a new API Key
   - Update all environment variables using that Key

2. Remove from Git history
   git filter-branch --force --index-filter \
     "git rm --cached --ignore-unmatch .env" \
     --prune-empty --tag-name-filter cat -- --all

   Or use the safer BFG Repo-Cleaner:
   bfg --delete-files .env
   git reflog expire --expire=now --all
   git gc --prune=now --aggressive

3. Force push the cleaned history
   git push --force --all

4. Check for abnormal usage

   - Review API usage logs for any abnormal calls
   - Check billing records for any abnormal charges

5. Notify the team

   - Inform all developers to re-clone the repository
   - Update keys in all deployment environments

Team Security Guidelines

Sharing settings.json Template

Create a unified security baseline configuration for the team, committed to the project's .claude/settings.json:

{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm run:*)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(python -m pytest:*)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(**/*credential*)",
      "Write(.git/**)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Bash(ssh:*)",
      "Bash(scp:*)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date -Iseconds) TOOL_USE: $TOOL_INPUT\" >> .claude/audit.log"
          }
        ]
      }
    ]
  }
}

Team members can add personal configuration in ~/.claude/settings.json (user-level), but project-level Deny rules cannot be overridden.

Security Configuration in CI/CD

When using Claude Code in CI/CD environments (Headless mode), security configuration is even more important:

# GitHub Actions example
name: AI Code Review
on: [pull_request]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - name: Run Claude Code Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          claude --print --dangerously-skip-permissions \
            "Please review the code changes in this PR, providing feedback on security and code quality" \
            < /dev/null

CI/CD Security Considerations:

Item Description
API Key Management Use CI/CD platform Secrets (like GitHub Secrets), don't hardcode
Least Privilege Claude Code in CI environments should only have read-only permissions, should not modify code
Network Isolation Restrict outbound network access for CI environments
Timeout Settings Set reasonable execution time limits to prevent abnormally long runs
Log Cleanup Ensure sensitive information is not included in CI logs

Headless Mode Security Considerations

Headless mode (--print or -p) is used for non-interactive scenarios with no human in the loop to confirm operations. Extra care is needed when using it:

# Safe Headless usage β€” read-only operations
claude --print "Analyze the code structure in the src/ directory"

# For write operations, limit to specific files
claude --print "Fix the type error in src/utils.py"

# Be clearly aware of the risks when using --dangerously-skip-permissions
claude --print --dangerously-skip-permissions "Run npm test"

The reason --dangerously-skip-permissions has such a long parameter name is to remind you: skipping permission checks is dangerous. Only use it when you completely control the input prompt and fully understand the possible operations.

Enterprise Security

Network Access Control

If your enterprise has strict network security policies, you can control Claude Code's network access in the following ways:

1. Proxy Configuration

Set HTTP proxy through environment variables, so all network requests go through the proxy for easier auditing and control:

# Set in .bashrc or .zshrc
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1,.company.com

2. Firewall Rules

On the enterprise firewall, you can allow Claude Code to access only necessary domains:

# Domains that must be allowed
api.anthropic.com         # Anthropic API
*.claude.ai               # Claude services

# Optional allows (based on features used)
registry.npmjs.org         # npm package installation
pypi.org                   # Python package installation
github.com                 # Git operations

3. Disable Network Commands in settings.json

As an additional security layer, block common network tools in Deny rules:

{
  "permissions": {
    "deny": [
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Bash(nc:*)",
      "Bash(ssh:*)",
      "Bash(scp:*)",
      "Bash(rsync:*)",
      "Bash(ftp:*)",
      "Bash(telnet:*)"
    ]
  }
}

Log Auditing

For enterprises with higher compliance requirements, it's recommended to establish a complete audit logging system:

1. Operation Logs

Record all tool invocations through Hooks:

{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"\nimport json, os, datetime\nlog_entry = {\n    'timestamp': datetime.datetime.now().isoformat(),\n    'user': os.environ.get('USER', 'unknown'),\n    'tool': os.environ.get('TOOL_NAME', 'unknown'),\n    'input': os.environ.get('TOOL_INPUT', '')[:500]\n}\nwith open(os.path.expanduser('~/.claude/audit.jsonl'), 'a') as f:\n    f.write(json.dumps(log_entry, ensure_ascii=False) + '\\n')\n\""
          }
        ]
      }
    ]
  }
}

2. Session Logs

Claude Code itself retains session history, stored in the ~/.claude/ directory. Enterprises can back up these logs periodically.

3. Change Tracking

Combined with Git, all file changes have complete tracking records. It's recommended that Claude Code's modifications be made on separate branches and merged through the PR process.

Compliance Considerations

Using AI programming tools may involve the following compliance issues:

Data Security

Risk Mitigation Measures
Code uploaded to the cloud Claude Code does not upload your code to Anthropic servers (except context in API requests)
Sensitive data leakage Use .claudeignore to exclude sensitive files; block reading of .env and key files in Deny rules
Sensitive information in API requests Avoid pasting passwords, tokens, and other sensitive information in conversations

Intellectual Property

  • Outputs from Claude Code (generated code) belong to the user
  • Be mindful that generated code may contain copyrighted content
  • For critical business code, human review is recommended before use

Industry Compliance

  • Regulated industries like finance and healthcare may have additional AI usage restrictions
  • Confirm whether your organization has policies regarding AI tool usage
  • Maintain audit logs to meet compliance review requirements

Quick Security Configuration Reference

Finally, here's a quick reference security configuration checklist:

Minimum Security Configuration for Individual Developers

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)"
    ]
  }
}
{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm run:*)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)",
      "Read(**/*.pem)",
      "Write(.git/**)",
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)",
      "Bash(curl:*)",
      "Bash(wget:*)"
    ]
  }
}

High Security Enterprise Configuration

{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test)",
      "Bash(git status)",
      "Bash(git diff)"
    ],
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(secrets/**)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(**/*credential*)",
      "Read(**/*password*)",
      "Write(.git/**)",
      "Write(.github/**)",
      "Bash(rm:*)",
      "Bash(git push:*)",
      "Bash(git reset:*)",
      "Bash(git checkout -- :*)",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Bash(ssh:*)",
      "Bash(scp:*)",
      "Bash(nc:*)",
      "Bash(python -c:*)",
      "Bash(node -e:*)",
      "Bash(eval:*)",
      "Bash(exec:*)",
      "Bash(sudo:*)",
      "Bash(chmod:*)",
      "Bash(chown:*)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"import json,os,datetime; f=open(os.path.expanduser('~/.claude/audit.jsonl'),'a'); f.write(json.dumps({'ts':datetime.datetime.now().isoformat(),'user':os.environ.get('USER','?'),'tool':os.environ.get('TOOL_NAME','?'),'input':os.environ.get('TOOL_INPUT','')[:500]},ensure_ascii=False)+'\\n'); f.close()\""
          }
        ]
      }
    ]
  }
}

Frequently Asked Questions

Q: Does Claude Code send my code to Anthropic's servers?

A: Claude Code communicates with Anthropic via API, and conversation context (including code snippets you reference) is sent as part of API requests. However, Anthropic does not use data submitted via API to train models (according to Anthropic's terms of service). If you use Claude Code through a proxy service like QCode, please refer to that service's privacy policy.

Q: Can Claude bypass Deny rules in settings.json?

A: Under normal usage, no. Deny rules are enforced at the system level, and Claude cannot bypass them through prompt engineering or other means. However, if you manually select bypassPermissions mode or use the --dangerously-skip-permissions parameter, these rules no longer apply.

Q: What's the difference between .claudeignore and Deny rules?

A: .claudeignore makes Claude completely unaware of the existence of files (similar to how .gitignore works for Git), while Deny rules reject access when Claude attempts to access files. Use .claudeignore if you don't want Claude to know certain files exist; use Deny rules if you just don't want it to modify them.

Q: In multi-person collaboration, what if everyone has different settings.json?

A: The project-level .claude/settings.json is committed to Git, ensuring the team shares a unified security baseline. Individuals can add additional configuration in their personal ~/.claude/settings.json, but cannot override project-level Deny rules.

Q: Is it more secure to use Claude Code inside a Docker container?

A: Yes. Docker provides an additional layer of isolation, so even if Claude Code performs unexpected operations, the impact is contained within the container. Recommended practices in containers: use read-only mounts for source code directories, restrict network access, run as a non-root user.

πŸš€
Get Started with QCode β€” AI Coding Assistant
Official Claude Code relay, fast and reliable, ready to use
View Pricing Plans β†’ Create Account