Claude Code Guide

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.

View the Project on GitHub ytrofr/claude-code-guide

Security Checklist

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.

Why Scan

A mature setup (dozens of rules, skills, and hooks) accumulates risk silently:

Scan monthly. Scan after every plugin/skill install. Scan after upgrading Claude Code — new features shift the threat model.

What to Scan For

1. Plaintext secrets

API keys, tokens, passwords, connection strings — anywhere under ~/.claude/.

2. OAuth credential files in git

3. Overly broad permissions

permissions.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:*).

4. Missing or weak deny list

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.

5. Hook scripts doing dangerous things

Hooks run unattended. Scan every file in ~/.claude/hooks/ and inline hook commands in settings.json for:

6. MCP servers

MCP 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:

7. Skill and rule bodies

Skills and rules are loaded into context unmodified. A malicious or careless body can plant instructions:

Manual Scan Recipes

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'

Automated Scanning

Two skills do most of the manual work:

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.

Pre-Push Discipline

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.

CC Version-Specific Notes

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.

Incident Response

If you find a leaked credential:

  1. Rotate immediately. Don’t clean up first — rotate the token at the provider (Anthropic console, Google OAuth, etc.) before anything else. Old token is now untrusted.
  2. Archive the leak. Copy the offending file/commit to an offline archive for audit. Do not force-push the secret out of history until it’s rotated — force-push can race with a cloner.
  3. Scrub history. Once rotated, use git filter-repo or BFG to purge the file from all refs. Then git push --force-with-lease to the public remote.
  4. Audit reach. Check recent provider logs for any access using the leaked token. Treat any unexpected use as hostile.
  5. Fresh init. Regenerate the Claude Code setup that leaked it — run a clean ~/.claude/ tree from a known-good backup repo, then re-apply skills/rules file by file, re-scanning at each step.
  6. Write it up. Add a note to your project memory describing what leaked, how, and what changed. This is the pattern that stops the next one.

See Also