The complete guide to Claude Code. Opus 4.7, Sonnet 4.6, Haiku 4.5. 1M token context window. 27 hook events. 43 production-tested chapters across 6 topical Parts (Foundation, Workflow, Extension, Context Engineering, Advanced, Reference). Three install tiers. CC 2.1.121+ compatible.
Detecting secrets, permission gaps, and injection risks in your ~/.claude/ setup.
Your Claude Code configuration is executable surface area: hooks run shell, MCP servers inherit environment, skills can carry hidden directives, and permissions.allow entries gate every tool call. This chapter lists what to scan for, how to scan, how to automate it, and what to do when something leaks.
A mature setup (dozens of rules, skills, and hooks) accumulates risk silently:
rm -f under >/dev/nullANTHROPIC_API_KEYBash(docker *) lets the model run docker exec -it ... sh.credentials.json can end up in git history if .gitignore is misconfiguredScan monthly. Scan after every plugin/skill install. Scan after upgrading Claude Code — new features shift the threat model.
API keys, tokens, passwords, connection strings — anywhere under ~/.claude/.
~/.claude/settings.json and ~/.claude/settings.local.json~/.claude.json (MCP server definitions live here, not in settings.json).claude/settings.json files~/.bashrc / ~/.zshrc exports (these leak into every subprocess unless scrubbed — see CC 2.1.98 note below)env blocks.credentials.json, credentials.json, token.json, *.pem, id_rsa*HEAD still lives in git log -ppermissions.allow entries that amount to arbitrary code execution:
| Pattern | Risk |
|---|---|
Bash(*) |
Full shell access |
Bash(docker *) |
docker exec -it <container> sh |
Bash(node *) |
node -e "require('child_process').exec(...)" |
Bash(curl *) |
Arbitrary network fetch + pipe-to-shell |
Bash(npx *) |
Arbitrary package execution |
Bash(sudo:*) |
Privilege escalation |
Prefer narrow patterns: Bash(docker ps), Bash(docker logs *), Bash(curl -sf http://localhost:*).
A deny list backstops the allow list. CC 2.1.99 made permissions.deny authoritative — it overrides PreToolUse “ask” hooks, so a correctly written deny entry cannot be bypassed by prompting a hook. But CC 2.1.98 also closed six bypass vectors in the deny matcher itself; your patterns must account for them.
Minimum deny set (every entry should resist CC 2.1.98-era bypass attempts):
"deny": [
"Bash(rm -rf /)*",
"Bash(sudo:*)",
"Bash(chmod 777 *)",
"Bash(> /dev/*)",
"Bash(ssh *)",
"Bash(killall node)*",
"Bash(pkill -f node)*",
"Bash(pkill node)*"
]
The closing * and :* variants guard against compound-command smuggling and whitespace padding that earlier CC versions allowed through.
Hooks run unattended. Scan every file in ~/.claude/hooks/ and inline hook commands in settings.json for:
rm -f, rm -rf, > /dev/*, 2>/dev/null masking errors on deletesgit config --global mutationscurl, wget, nc, /dev/tcp/, /dev/udp/cat ~/.aws/*, cat ~/.ssh/*, cat **/credentials*env >, printenv >, unscoped $* passed to external commandsMCP definitions live in ~/.claude.json (registered via claude mcp add), not settings.json. The mcpServers key in settings.json is silently ignored — do not rely on it for audit.
Risks to check in ~/.claude.json:
@latest tag on npm MCP servers → supply-chain risk; pin versionsautoApprove: true on any toolenv block → server inherits every env var (including ANTHROPIC_API_KEY, DB credentials)claude mcp add)Skills and rules are loaded into context unmodified. A malicious or careless body can plant instructions:
curl https://..., <img src=http://...>)allowed-tools frontmatter requesting Bash(*)Copy-paste greps. Adjust paths as needed.
# 1. Plaintext API keys under ~/.claude/
grep -rEn 'sk-ant-(oat|ort|api)[0-9]{0,2}-[A-Za-z0-9_-]{30,}' <USER_HOME>/.claude/ <USER_HOME>/.claude.json 2>/dev/null
grep -rEn '(api[_-]?key|secret|password|token)["\s:=]+["A-Za-z0-9_-]{20,}' <USER_HOME>/.claude/ 2>/dev/null
# 2. SSH keys / certs accidentally stored
grep -rEn 'BEGIN (RSA|OPENSSH|DSA|EC|PRIVATE) KEY' <USER_HOME>/.claude/ 2>/dev/null
# 3. Broad allow entries
jq '.permissions.allow[]? | select(. | test("\\*\\)$|Bash\\(\\*|Bash\\((docker|node|curl|npx|bash) "))' \
<USER_HOME>/.claude/settings.json
# 4. MCP servers with @latest or missing env
jq '.mcpServers | to_entries[] | select(
(.value.args // [] | any(. | test("@latest$"))) or
(.value.env == null)
) | .key' <USER_HOME>/.claude.json
# 5. Hook scripts doing network egress or unsafe deletes
grep -rEn '(curl|wget|nc|/dev/tcp|/dev/udp|rm -rf|rm -f.*>/dev/null)' <USER_HOME>/.claude/hooks/ 2>/dev/null
# 6. OAuth credential files tracked in git (run in each project)
git ls-files | grep -Ei '(credentials|token|secret).*\.(json|yaml|yml|env)$'
git log --all --diff-filter=A --name-only | grep -Ei 'credentials\.json|token\.json'
Two skills do most of the manual work:
/audit-stack — runs the full sweep above (permissions, hooks, MCP, skills, git hygiene) and produces a scored report. Use monthly and after major changes./gitignore-anchor-audit — catches unanchored top-level directory entries in .gitignore that recursively shadow nested paths (the exact misconfiguration that lets .credentials.json sneak into git). Use when adding to .gitignore, after finding a tracked file you didn’t expect, or on a new repo.For public/shared repos, also run a third-party scanner periodically:
npx ecc-agentshield scan <USER_HOME>/.claude/
It scores across secrets, permissions, hooks, MCP servers, and skill bodies. Triage anything below 75 this month; anything below 50 this week.
When pushing a config repo (shared-setup, dotfiles, or the guide repo itself):
# Block real token material in staged diff — pattern strings are fine, token bodies are not
git diff --cached | grep -E 'sk-ant-oat01-[A-Za-z0-9_-]{30,}|sk-ant-ort01-[A-Za-z0-9_-]{30,}|sk-ant-api[0-9]{2}-[A-Za-z0-9_-]{30,}' \
&& { echo "REAL TOKEN IN DIFF — STOP"; exit 1; } || echo "no real tokens in diff"
# Block SSH/PEM keys
git diff --cached | grep -E 'BEGIN (RSA|OPENSSH|DSA|EC|PRIVATE) KEY' \
&& { echo "PRIVATE KEY IN DIFF — STOP"; exit 1; } || echo "no private keys"
# Block internal paths if repo is public
git diff --cached | grep -E '/home/[a-z]+/|/Users/[a-z]+/' \
&& echo "WARN: internal path in diff" || echo "no internal paths"
Wire the first two into a pre-push hook for any repo that has ever held a credential file.
Several security-relevant changes landed in the 2.1.98–2.1.111 window. Update your mental model:
| Version | Change | What it means for audits |
|---|---|---|
| 2.1.98 | CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 strips Anthropic API creds from subprocesses + triggers PID namespace isolation on Linux |
Set this in .bashrc to stop shell subprocesses from inheriting API keys. Trade-off: incompatible with skipDangerousModePermissionPrompt: true — if you rely on that for automation, the scrub stays off and env-based MCP auth is still exposed. Pick one. |
| 2.1.98 | Six deny-list bypass vectors closed (compound commands, backslash escapes, /dev/tcp, /dev/udp, env-prefix commands, whitespace padding) |
Old deny entries like "Bash(ssh *)" without the closing-paren variant may have been bypassable. Re-audit using the minimum deny set above. |
| 2.1.99 | Settings resilience: unrecognized hook event names fail gracefully instead of nuking the whole settings.json |
A typo in a hook matcher no longer silently disables all your hooks. But a malicious skill that injects a malformed hook now fails quietly — scan hook output regularly. |
| 2.1.99 | permissions.deny overrides PreToolUse “ask” hooks |
Any advice assuming a hook could gate a deny-listed tool is wrong. Deny wins. Move “ask”-style prompts to allowed tools; keep deny for hard blocks. |
| 2.1.105 | PreCompact hook can block compaction via {"decision":"block"} |
Write a PreCompact hook that refuses compaction when context contains secrets-shaped strings or OAuth tokens — prevents a compacted summary from persisting a leaked credential. |
| 2.1.105 | Skill description budget 250 → 1536 chars | Longer descriptions can now hide prompt injection text; include skill descriptions in your grep for hidden directives. |
If you find a leaked credential:
git filter-repo or BFG to purge the file from all refs. Then git push --force-with-lease to the public remote.~/.claude/ tree from a known-good backup repo, then re-apply skills/rules file by file, re-scanning at each step.settings.json structure, hook catalogue/audit-stack skill — automated monthly scan/gitignore-anchor-audit skill — .gitignore recursive-shadow detection