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:
- The Key will be committed to Git history (even if deleted later)
- The Key is visible to Claude Code and may appear in logs or analysis results
- 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:*)"
]
}
}
Recommended Configuration for Team Projects¶
{
"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.