L1
·
Quiz
·
Lab
L2
·
Quiz
·
Lab
L3
·
Quiz
·
Lab
L4
·
Quiz
·
Lab
Module Test
Module 2 · Lesson 1

The Hook System: Pre and Post Execution Control

Before Claude touches a file, something else can run. After it finishes, something else can verify. Hooks are where policy lives.
What happens between your instruction and Claude's action — and why does it matter?

In mid-2024, Cloudflare's internal platform team began piloting AI-assisted coding agents for their edge infrastructure. A senior engineer, Paulo Frazão, described a near-miss in a public retrospective: an AI agent had been given broad file-write permissions to update configuration templates. One afternoon it modified a production YAML file that controlled routing rules for over two million domains. The change was syntactically valid. The agent had no reason to pause. There was no hook to intercept the write and check whether the target path was in a protected namespace. The file was caught fifteen minutes later by a human reviewer before deployment — but the incident prompted the team to implement pre-execution hooks that classified every file path before any write was allowed to proceed.

Frazão later noted: "The agent wasn't wrong. It did exactly what we told it to do. We just hadn't told it everything it needed to know about context."

What Hooks Are

Claude Code's hook system is a programmable interception layer that runs shell commands or scripts at defined points in Claude's execution lifecycle. Hooks are declared in your settings.json (at the project or user level) and fire automatically — Claude itself does not decide whether they run. That distinction is critical: hooks are enforced by the framework, not negotiated with the model.

There are two primary hook positions. A PreToolUse hook fires before Claude executes any tool call — file writes, bash commands, web fetches, anything. A PostToolUse hook fires immediately after a tool call completes, before Claude continues processing the result. This bracketing gives you two distinct moments of control: one to decide whether to allow the action, one to verify what the action produced.

PreToolUseA hook that intercepts a tool call before execution. Can inspect the proposed action, log it, transform parameters, or block it entirely by exiting with a non-zero status code.
PostToolUseA hook that runs after a tool call completes but before Claude processes the output. Used for audit logging, output sanitization, side-effect verification, or triggering secondary actions.
Anatomy of a Hook Declaration

Hooks are configured in the hooks key of your Claude Code settings file. Each hook entry specifies a matcher — which tool or tool pattern triggers it — and a command — the shell string to execute. The command receives context about the tool call via environment variables or stdin (depending on configuration).

A minimal PreToolUse hook that logs every file write looks like this in settings.json:

Configuration Pattern

"hooks": { "PreToolUse": [{ "matcher": "Write", "hooks": [{ "type": "command", "command": "echo \"$CLAUDE_TOOL_INPUT\" >> /var/log/claude-writes.log" }] }] } — The matcher "Write" targets the Write tool specifically. Using "*" matches all tools. The command receives the full JSON tool input via the CLAUDE_TOOL_INPUT environment variable.

How Blocking Works

A hook command that exits with status code 0 is treated as a pass — Claude continues normally. A hook that exits with any non-zero status code blocks the tool call. Claude receives a message that the action was blocked, and it can attempt an alternative approach or report back to the user. This design means any existing Unix tool or script can function as a gate: a Python validator, a regex filter, a database lookup, or a simple grep.

This is exactly what Cloudflare's team implemented after Frazão's incident: a pre-write hook that ran a path-classification script. Paths matching production namespaces (/etc/, /var/cf-config/, specific S3 bucket patterns) caused the hook to exit 1, blocking the write and logging the attempt with full context for review.

Design Principle

Hooks implement the separation of mechanism and policy. Claude's mechanism is powerful and general-purpose. Your hooks are where organizational policy lives — access rules, audit requirements, compliance checks. Neither should be tangled into the other. A well-designed hook system means you can change policy without modifying prompts, and upgrade Claude without rewriting your safety layer.

Hook Scope: Project vs. User Level

Hooks can be declared at two scopes. Project-level hooks live in .claude/settings.json within the repository and apply to all users working in that project. They are version-controlled alongside code and represent team-wide policy. User-level hooks live in ~/.claude/settings.json and apply to everything that user runs, regardless of project. User hooks are appropriate for personal audit logging, individual security preferences, or tooling that integrates with a developer's own environment (IDE plugins, local secret scanners).

When both scopes have hooks for the same event, both run. Project hooks fire first, then user hooks. If either blocks, the action is blocked.

PostToolUse: Verification and Side Effects

Post-execution hooks are underused but powerful. After Claude writes a file, a PostToolUse hook can run a linter, a type-checker, a secret scanner (like trufflehog or gitleaks), or a custom validation script. If validation fails, the hook can write a corrective message back into Claude's context so the model can self-repair — turning what would have been a silent error into a feedback loop.

In 2024, the team at Temporal Technologies used PostToolUse hooks to run their internal API contract validator after every TypeScript file write. When Claude generated code that violated a gRPC contract, the hook output the validation error to stdout (which Claude Code surfaces as tool output), and Claude would immediately revise the file. Engineers reported that this pattern reduced code review cycles by catching a category of error that had previously only been caught at CI time.

Lesson 1 Quiz

The Hook System: Pre and Post Execution Control
1. A PreToolUse hook exits with status code 1. What does Claude Code do?
Correct. A non-zero exit code from a PreToolUse hook blocks the tool call. Claude receives notification that the action was blocked and can attempt an alternative.
Not quite. A non-zero exit from a PreToolUse hook blocks the tool call entirely — it does not log and continue, retry, or prompt the user.
2. Where are project-level hooks stored in a Claude Code repository?
Correct. Project-level hooks live in .claude/settings.json at the repository root, making them version-controlled and shared across the team.
Not quite. ~/.claude/settings.json is user-level scope. Project hooks live in .claude/settings.json inside the repository itself.
3. What does a PostToolUse hook receive about the completed tool call?
Correct. PostToolUse hooks receive full context — both what was passed to the tool and what it returned — allowing for meaningful validation and verification.
Not quite. PostToolUse hooks receive both the tool input and output as context, enabling real verification of what the tool produced.
4. When both project-level and user-level hooks are configured for the same event, what happens?
Correct. Both scopes run with project hooks firing first, then user hooks. A block from either scope is sufficient to prevent the action.
Not quite. Both scopes execute — project hooks run first, user hooks second. If either produces a non-zero exit, the action is blocked.
5. What design principle do hooks primarily embody?
Correct. Hooks separate mechanism (what Claude can do) from policy (what it should be allowed to do in your specific context), keeping each cleanly maintainable.
Not quite. The core design principle is separation of mechanism and policy — hooks keep organizational rules out of prompts and AI behavior out of policy files.

Lab 1: Designing Hook Policies

Practice session — discuss hook strategy with your AI lab assistant

Your Scenario

You are a platform engineer at a fintech startup. Claude Code is being introduced for backend development. Your team handles PCI-compliant payment processing. You need to design a hook strategy that prevents Claude from accidentally touching production config files or writing code with hardcoded secrets.

Suggested starting point: "I need to design PreToolUse hooks that block writes to paths matching /etc/payments/ or containing the string 'prod'. Walk me through how to structure this in settings.json and what the blocking script should check."
Hook Design Lab
Lesson 1
Welcome to the Hook Design Lab. I'm here to help you think through pre- and post-execution hook strategies for Claude Code. Describe your environment or constraints and we'll work through a concrete hook configuration together. What are you trying to protect or enforce?
Module 2 · Lesson 2

Permission Scopes and the Allow/Deny Architecture

Not every tool should be available in every context. Permission scopes let you shape what Claude can even attempt.
How do you give Claude enough power to be useful without giving it the keys to everything?

In late 2023, Notion ran an internal pilot of an AI coding agent for their backend infrastructure team. One of their senior engineers, Akosua Mensah, documented the pilot's first major stumble in an internal postmortem that later became a public talk at a developer conference. The agent had been given access to a broad bash execution tool with no scope restrictions. Within a week, a routine "clean up unused imports" task led the agent — following a chain of perfectly reasonable intermediate steps — to run find / -name "*.pyc" -delete across the entire development environment, including a mounted network share that contained archived experiment logs used by the ML team.

No secrets were exposed. No production system was touched. But three weeks of experiment logs were gone, and the recovery process consumed two days of engineering time. Mensah's postmortem concluded with a single sentence that became something of a mantra for the team: "Give the agent a scalpel, not a chainsaw."

What Permission Scopes Control

Claude Code's permission system operates at the tool level. You can explicitly allow or deny specific tools, and you can configure allowed path patterns for file operations. The system uses an allowlist and a denylist that work together: anything on the denylist is always blocked; anything not on the allowlist (in strict mode) is also blocked. This gives you two complementary control strategies.

The configuration key allowedTools takes an array of tool names or patterns. The key deniedTools takes the same structure but blocks regardless of other settings. You can also configure allowedDirectories to constrain file system access to specific path prefixes.

allowedToolsAn explicit list of tools Claude is permitted to use. When set, Claude cannot call any tool not in this list — a whitelist model. Supports wildcards for pattern matching.
deniedToolsAn explicit list of tools Claude is never permitted to use, regardless of any other configuration. Takes precedence over allowedTools. A blacklist that cannot be overridden by model judgment.
allowedDirectoriesConstrains all file system operations (Read, Write, Edit, List) to path prefixes in this list. Attempts to access paths outside these prefixes are blocked at the framework level.
The Principle of Least Privilege

The Notion incident is a textbook illustration of why the principle of least privilege applies to AI agents as much as it does to human operators or service accounts. The agent should receive only the permissions necessary to accomplish the current task — no more. When the task changes, permissions should be re-evaluated.

In practice, this means designing different permission profiles for different task types. A documentation-writing session needs Read access to source files and Write access to a docs/ directory. It does not need Bash execution or access to environment variables. A refactoring session needs Read and Edit for source files. It does not need network access or the ability to modify CI configuration. You would not give a contractor building a deck access to every room in your house; the same logic applies here.

Configuration Example

A locked-down refactoring profile in settings.json: "allowedTools": ["Read", "Edit", "Bash(grep *)", "Bash(find *)"] with "allowedDirectories": ["./src", "./tests"] — This gives Claude exactly what it needs for code navigation and editing within the source tree, with bash restricted to read-only search operations.

Bash Tool Scoping with Patterns

The Bash tool deserves special attention because it is both the most powerful and the most dangerous tool in Claude's default kit. Claude Code supports command-level scoping for Bash through the pattern syntax Bash(command pattern). This allows you to permit specific bash commands while denying others.

For example, Bash(npm test*) permits running test scripts but not arbitrary npm commands. Bash(git log*) permits reading git history but not commits or pushes. Bash(grep *) permits read-only text search. This granularity was not available in early versions of Claude Code and was specifically introduced in response to developer feedback about the risks of broad bash access — the exact category of risk that produced the Notion incident.

Deny Takes Precedence

A common question: what happens if a tool appears in both allowedTools and deniedTools? The answer is unambiguous — deny always wins. This design choice is deliberate. It means that a cautious default can always be enforced by adding to the denylist, without having to audit or modify every allowlist across all contexts. Security teams can add a global user-level deny that overrides any project-level allow. This is the correct security posture: the most restrictive applicable rule governs.

Operational Pattern

Many teams adopt a task-scoped session model: before each major Claude Code session, a short wrapper script sets the appropriate settings.json for the task type (docs, refactor, test-writing, migration) and launches Claude with that profile. The session is scoped to the minimum necessary permissions. When the session ends, the default restrictive profile is restored. This is analogous to how database administrators use role-based access: you don't run every query as superuser.

Monitoring Denied Actions

Permission denials are not just safety events — they are signals. When Claude repeatedly attempts to use a denied tool, it often means the task scope is wrong (the model needs something you haven't provided), or the task description is ambiguous (Claude is interpreting the request differently than you intended), or — in adversarial scenarios — that a prompt injection has occurred and is attempting to break out of constraints. Logging denied tool attempts with full context (what tool, what arguments, what was in the conversation at that point) provides audit data that is genuinely useful for both debugging and security review.

Lesson 2 Quiz

Permission Scopes and the Allow/Deny Architecture
1. A tool appears in both allowedTools and deniedTools. What happens when Claude attempts to use it?
Correct. Deny always wins. deniedTools takes precedence over allowedTools regardless of order, ensuring that security rules cannot be overridden by allow configurations.
Not quite. The core rule is: deny always wins. A tool in deniedTools is blocked even if it also appears in allowedTools.
2. What does the configuration "allowedDirectories": ["./src", "./tests"] accomplish?
Correct. allowedDirectories is a path prefix allowlist — Claude's file operations (Read, Write, Edit, List) are restricted to these path prefixes only.
Not quite. allowedDirectories is a whitelist of permitted path prefixes. Operations outside these directories are blocked, not operations within them.
3. What does the pattern "Bash(grep *)" in allowedTools permit?
Correct. The Bash(command pattern) syntax scopes bash access to commands matching the pattern. "Bash(grep *)" allows grep invocations only, a read-only search operation.
Not quite. The Bash(pattern) syntax matches against the beginning of the bash command string. "Bash(grep *)" allows commands starting with "grep" only.
4. Which security principle does the Notion "chainsaw vs scalpel" incident most directly illustrate?
Correct. The Notion incident is a direct illustration of least privilege failure — the agent had far more permission (broad bash access) than was needed for the task (cleaning unused imports).
Not quite. The incident illustrates least privilege: the agent was given broad bash access for a task that only required narrow file editing permissions.
5. Why are repeated denied tool attempts significant beyond simple access control?
Correct. Denied tool attempts are diagnostic signals — they reveal whether the task scope is misconfigured, the instructions are ambiguous, or an adversarial input is attempting to escape constraints.
Not quite. Denied attempts are valuable signals about task scope problems, ambiguous instructions, or adversarial prompt injection attempts — not just access control events.

Lab 2: Building Permission Profiles

Practice session — design task-scoped permission configurations

Your Scenario

You are setting up Claude Code for a team building a Node.js API. You need to create at least two permission profiles: one for writing unit tests (safe, limited) and one for database migration tasks (broader but still scoped). Each profile should specify allowedTools, deniedTools, and allowedDirectories appropriately.

Suggested starting point: "Help me design a permission profile for Claude Code when it's writing unit tests for a Node.js API. The tests should only be able to read source files and write to the tests/ directory. It should never execute database commands or modify package.json."
Permission Profile Lab
Lesson 2
Welcome to the Permission Profile Lab. I can help you design allowedTools, deniedTools, and allowedDirectories configurations for different task types. Tell me about your application and the kinds of tasks you want to scope, and we'll build specific settings.json permission profiles together.
Module 2 · Lesson 3

Human-in-the-Loop: When Claude Must Pause

Full automation is not always the goal. The art is knowing which decisions must stay human.
How do you design an automation pipeline that asks for permission before crossing lines that matter?

In the spring of 2024, a development team using Cursor's agent mode to automate database schema migrations encountered a failure mode that became widely discussed in developer communities. The agent had been tasked with "migrate all remaining legacy tables to the new schema." It proceeded through seventeen tables successfully in a staging environment, then — without pausing — began the same process on a production database connection that had been left open in the same terminal session. The session context had not clearly delineated the environment boundary.

Four tables were migrated before a team member noticed the production database metrics changing. The migrations themselves were structurally correct, but they had not been reviewed for production data volume and locking behavior. The rollback took six hours. The team's incident review produced one core recommendation: for any action targeting a production resource, the agent must pause and explicitly confirm the target environment with a human before proceeding. Not a warning. Not a log entry. A pause that requires active acknowledgment.

The Confirmation Gate Pattern

Claude Code supports human-in-the-loop control through two mechanisms. The first is the --ask-permission flag (or equivalent settings configuration), which causes Claude to request user confirmation before executing any tool call. This is maximally safe but maximally interruptive — suitable for high-stakes or unfamiliar tasks, not for routine automation.

The second and more practical mechanism is hook-based confirmation gates: PreToolUse hooks that detect high-stakes conditions and pause for explicit confirmation rather than allowing or blocking outright. A hook can write a prompt to the terminal and wait for input before returning, effectively implementing a per-operation confirmation workflow that is triggered only when the conditions warrant it.

--ask-permission modeA Claude Code launch flag that requires explicit user confirmation before every tool execution. Appropriate for onboarding, auditing, or executing untrusted tasks. Not practical for routine automation workflows.
Confirmation Gate HookA PreToolUse hook that, instead of unconditionally blocking or passing, prompts for human input when specific conditions are met (e.g., target is production, action is destructive, file size exceeds threshold). Passes on 'y', blocks on anything else.
Detecting Irreversibility

The key design challenge for confirmation gates is identifying which conditions should trigger them. The most useful heuristic is irreversibility: actions that cannot be easily undone deserve more caution than actions that can. Deleting a file without git tracking is irreversible. Modifying a production database record is hard to reverse. Running a test suite is freely reversible. This maps directly to how confirmation gates should be configured.

In practice, teams often define confirmation-triggering conditions along two axes: environment (is the target production, staging, or development?) and action type (is this destructive, modifying shared state, or accessing sensitive data?). The intersection of high-risk environment and destructive action is where confirmation gates earn their cost.

Implementation Pattern — Environment Detection Hook

A PreToolUse hook for the Write tool that checks whether the target path contains "/prod/" or "/production/" in its name, then prompts: if [[ "$TARGET_PATH" == *"/prod/"* ]]; then read -p "Writing to production path $TARGET_PATH. Confirm? [y/N] " yn; [[ "$yn" == "y" ]] || exit 1; fi — This adds a single confirmation step only when the at-risk condition is present, leaving all other writes uninterrupted.

Structuring Tasks to Minimize Needed Interruptions

The better your task structure, the fewer interruptions you need. The Cursor incident happened in part because the task description did not make the environment boundary explicit. A well-scoped task would have said: "Migrate the following six tables in staging only. When complete, pause and show me the migration SQL for production review before any production execution." This is not just a safety practice — it produces better results, because Claude Code will write cleaner, more targeted migration code when the scope is precisely defined.

Teams at PlanetScale developed a pattern they called "dry run by default, wet run on demand" for AI-assisted schema migrations: every migration task was structured so that Claude's first output was always a dry run that showed exactly what would execute, requiring an explicit human instruction to proceed to actual execution. The dry run step was not a confirmation dialog — it was a designed phase in the task structure itself.

Timeout and Abandonment

One often-overlooked aspect of human-in-the-loop design is what happens when the human doesn't respond. In long-running automation jobs, Claude Code may encounter a confirmation gate while the operator has stepped away. The safest default is fail closed: if no response is received within a timeout period, the hook exits with a non-zero code, blocking the action and leaving the state unchanged. This is preferable to proceeding on timeout, which turns a safety pause into just a delay.

Your confirmation gate hooks should include a timeout parameter and a clear log message explaining why execution was halted, so the operator returning to the terminal understands immediately what happened and can restart the task with appropriate context.

Design Principle

Human-in-the-loop is not a fallback for when automation fails — it is a deliberate architectural choice for operations where human judgment adds value that automation cannot substitute. The question to ask for each class of operation is: "Would a reasonable person expect to be informed before this happens?" If yes, build the gate. The cost of an interruption is almost always lower than the cost of an unwanted irreversible action.

Lesson 3 Quiz

Human-in-the-Loop: When Claude Must Pause
1. What was the core failure mode in the Cursor agent database migration incident?
Correct. The agent correctly completed staging migrations then continued to the open production connection without pausing — there was no confirmation gate for the environment boundary crossing.
Not quite. The agent's SQL was structurally correct. The failure was the absence of a pause for human confirmation when crossing from staging to production.
2. What is the primary heuristic for deciding which operations need confirmation gates?
Correct. Irreversibility is the key heuristic. Deleting un-tracked files or modifying production data cannot be easily undone; these operations deserve confirmation gates. Running tests can be freely repeated.
Not quite. The primary heuristic is irreversibility — can the action be easily undone? If not, a confirmation gate is warranted regardless of file size or operation count.
3. In the PlanetScale "dry run by default" pattern, what is the purpose of the dry run phase?
Correct. The dry run is a designed task phase — not a dialog box — that surfaces exactly what will happen and requires explicit human instruction to proceed to real execution.
Not quite. The dry run phase is a structured task design that shows humans exactly what will execute, requiring a deliberate second instruction before actual execution begins.
4. What should a confirmation gate hook do if the human doesn't respond within the timeout period?
Correct. Fail closed is the safe default. On timeout, the hook blocks the action, leaves state unchanged, and logs a clear message so the returning operator understands what happened.
Not quite. A timeout should fail closed — block the action and leave state unchanged. Proceeding on timeout turns a safety mechanism into just a delay.
5. When is --ask-permission mode most appropriate vs. hook-based confirmation gates?
Correct. --ask-permission mode is maximally safe but maximally interruptive, best for onboarding and high-stakes tasks. Hook-based gates are targeted, triggering only when specific conditions warrant human review.
Not quite. --ask-permission interrupts every operation — useful for high-stakes or unfamiliar tasks. Hook gates are selective, triggering only when specific conditions (production target, destructive action) are met.

Lab 3: Designing Confirmation Gates

Practice session — design human-in-the-loop checkpoints for high-risk operations

Your Scenario

You are building an automation pipeline where Claude Code helps with database migrations for a SaaS product with 50,000 users. You need to design confirmation gate hooks that distinguish between development, staging, and production targets, and that handle the timeout/fail-closed pattern correctly.

Suggested starting point: "I need a PreToolUse hook for the Bash tool that detects when a command contains a production database connection string (matching 'prod-db' or 'DATABASE_URL_PROD') and pauses for confirmation. The hook should time out and fail closed after 30 seconds. Show me the bash script for this hook."
Confirmation Gate Lab
Lesson 3
Welcome to the Confirmation Gate Lab. I'll help you design human-in-the-loop hooks that detect high-risk conditions and pause for explicit confirmation. Describe your environment, the operations you want gated, and the conditions that should trigger a pause — we'll write the actual hook scripts together.
Module 2 · Lesson 4

Secrets, Credentials, and the Minimal Context Principle

What Claude doesn't know, it can't leak. What Claude can't reach, it can't misuse.
How do you give Claude Code the access it needs to work without exposing credentials it shouldn't have?

In April 2023, Samsung Semiconductor engineers made headlines for entirely the wrong reasons. Three separate incidents within a single month involved engineers pasting sensitive internal code into ChatGPT for assistance — including, in one case, source code containing database connection strings with production credentials, and in another, internal meeting notes about semiconductor yield problems. The information was sent to OpenAI's servers as part of the conversation context. Samsung subsequently banned the use of generative AI tools on company devices.

The incidents were not attacks. They were well-intentioned engineers doing their jobs — trying to get help with real problems. But they illustrate a failure mode that applies directly to Claude Code: when you give an AI model context, you are also giving it everything in that context. If your context includes credentials, those credentials have left the building.

How Claude Code Encounters Secrets

Claude Code is a code agent that reads files, executes commands, and processes their output. This creates multiple vectors through which secrets can enter Claude's context. The most common are: reading a .env file to understand a project's configuration, executing a command that outputs a token or key in its response, reading a configuration file that embeds API keys, or processing error output that includes authentication details.

In most cases, Claude doesn't need the actual value of a secret — it needs to know that a secret exists and what its name is. The distinction matters enormously. Claude can write code that uses process.env.STRIPE_SECRET_KEY without ever knowing the value of that key. Giving Claude the actual key provides no benefit to the task and creates unnecessary exposure.

Minimal Context PrincipleGive Claude Code only the information it needs to complete the current task. If it doesn't need the value of a secret, don't provide it. If it doesn't need access to a directory, exclude it. Unnecessary context is unnecessary risk.
Secret Scanning HookA PostToolUse hook that runs a secret detection tool (trufflehog, gitleaks, detect-secrets) over files Claude has just written, blocking any commit or further action if secrets are detected. Acts as a last line of defense against accidental credential embedding.
Protecting .env and Secrets Files

The most direct protection is to explicitly exclude secrets files from Claude's directory access. If your project uses a .env file at the root, and your allowedDirectories configuration includes ./src and ./tests but not the root directory or specific exclusions, Claude cannot read .env. This is often the correct configuration for development sessions focused on application code.

When Claude does need to read the project root — for configuration files, package.json, or similar — you can use a PreToolUse hook on the Read tool that blocks reads of specific file patterns. A hook that checks whether the target file matches *.env, *.pem, *secret*, *credential*, or *_key* in its filename and exits 1 on a match provides a targeted protection layer that doesn't require restructuring your directory permissions.

Hook Pattern — Secrets File Blocking

A PreToolUse hook on the Read tool: TARGET=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import sys,json;print(json.load(sys.stdin).get('file_path',''))") — then check the target against a pattern list. Matching patterns: .env, .env.*, *.pem, *.key, *secret*, *credential*, *.p12, *.pfx, id_rsa, id_ed25519. Exit 1 with a message if matched. This adds read protection without restructuring allowedDirectories.

Environment Variable Injection vs. File-Based Secrets

A safer pattern for secrets that Claude's generated code will reference is environment variable injection at runtime rather than file-based configuration. If Claude is writing code that needs to call an API, it should write code that reads the key from an environment variable. When you test that code, you inject the variable into the process environment directly — the value never appears in a file Claude can read.

This is consistent with the twelve-factor app methodology and with security best practices generally: credentials belong in the environment, not in files. For Claude Code workflows, it also means that even if Claude has full read access to your source tree, it will encounter only variable names, not values. The actual credentials live in your secrets manager, your CI pipeline's secret store, or your shell's exported environment — none of which Claude reads by default.

PostToolUse Secret Scanning

Despite best efforts, secrets occasionally end up in code — hardcoded during a debug session, inserted by a model following an ambiguous example, or copy-pasted from a legacy file. This is why a PostToolUse secret scanning hook is a valuable second line of defense. Tools like trufflehog (open source, maintained by Truffle Security), gitleaks, and detect-secrets (from Yelp) can scan a file for credential patterns in under a second.

Configuring gitleaks as a PostToolUse hook on the Write and Edit tools means that every file Claude writes is immediately scanned. If gitleaks finds a match, the hook exits non-zero, Claude is notified that a secret may have been embedded, and Claude can revise the file before it is ever committed. This catches the category of problem that the Samsung engineers encountered before it has any chance of leaving the local environment.

Layered Defense Model

No single control is sufficient. A complete secrets protection strategy for Claude Code combines: (1) allowedDirectories that exclude secrets files from Claude's reach; (2) PreToolUse hooks blocking reads of secrets-pattern filenames; (3) environment variable injection so code references names, not values; and (4) PostToolUse scanning for secrets in every file Claude writes. Each layer catches what slips through the others.

Audit Logging as a Safety Net

Even with all the above controls, maintaining an audit log of every file Claude reads and every command it executes provides a crucial recovery capability. If a secret is later found to have been exposed, the audit log tells you exactly when it happened, what Claude was doing, and what context it had. This supports incident response and also creates accountability that encourages disciplined configuration practices in the first place.

A simple audit log hook — a PreToolUse hook that appends a timestamped JSON record of every tool call to a rotating log file — costs almost nothing in performance and provides significant forensic value. Many teams maintain these logs alongside their CI artifacts for the duration of a project.

Lesson 4 Quiz

Secrets, Credentials, and the Minimal Context Principle
1. What was the primary security failure in the Samsung ChatGPT incidents of April 2023?
Correct. The incidents were not attacks — well-intentioned engineers sent sensitive material as context to an external service, not recognizing that anything in that context had left the company's environment.
Not quite. The incidents were not attacks. Engineers submitted sensitive code and credentials as part of their AI conversation context, which was transmitted to and stored by an external service.
2. According to the Minimal Context Principle, what should Claude receive when it needs to write code that calls a payment API?
Correct. Claude can write code referencing process.env.STRIPE_SECRET_KEY without knowing the key's value. The value provides no task benefit and creates unnecessary exposure.
Not quite. Claude needs the environment variable name to write correct code, but not the value. Providing the actual key creates exposure with no task benefit.
3. What is the recommended hook placement for secret scanning with tools like gitleaks or trufflehog?
Correct. A PostToolUse hook on Write and Edit tools scans each file Claude writes immediately after creation, catching secrets before they are ever committed or shared.
Not quite. Secret scanning hooks work best as PostToolUse hooks on Write and Edit — scanning files right after Claude writes them, before they can be committed.
4. Why does the twelve-factor app methodology align with Claude Code security best practices for credentials?
Correct. Twelve-factor keeps credentials in the process environment, not in files. Since Claude reads files but doesn't read the shell environment directly, code using env vars keeps actual credential values away from Claude.
Not quite. Twelve-factor stores credentials in environment variables, not files. Claude reads files — so code that references env vars means Claude never encounters actual credential values.
5. What is the forensic value of maintaining an audit log of all Claude Code tool calls?
Correct. Audit logs support incident response by providing a complete timeline of what Claude accessed and when, enabling precise determination of exposure scope if a secret is later found compromised.
Not quite. Audit logs support incident response — when a secret exposure is discovered, the log tells you exactly what happened, enabling precise scope assessment and recovery.

Lab 4: Secrets Protection Strategy

Practice session — design a layered secrets defense for your Claude Code environment

Your Scenario

You are a security engineer at a startup using Claude Code for backend development. Your codebase has .env files, AWS credential files (~/.aws/credentials), and several config files with embedded API keys from before your secrets management migration. You need to design the full four-layer secrets protection model for your Claude Code setup.

Suggested starting point: "I need to build a complete secrets protection strategy for Claude Code. My project has .env files at the root, AWS credentials in ~/.aws/, and some legacy config files with embedded keys in ./config/legacy/. Design the allowedDirectories, PreToolUse hook, PostToolUse scanning hook, and audit logging hook I should implement, with the actual configuration JSON and hook scripts."
Secrets Protection Lab
Lesson 4
Welcome to the Secrets Protection Lab. I'll help you design a layered defense against credential exposure in Claude Code environments. Describe your project structure, the kinds of secrets you handle, and your current security posture — we'll work through the specific allowedDirectories configuration, PreToolUse hooks, PostToolUse scanning, and audit logging together.

Module 2 Test

Hooks, Permissions, and Safe Automation — 15 questions · Pass at 80%
1. What is the fundamental difference between a PreToolUse hook and a PostToolUse hook?
Correct. PreToolUse intercepts before execution (can block), PostToolUse fires after execution (can verify). The timing difference defines their use cases.
The key distinction is timing and capability: PreToolUse fires before and can block; PostToolUse fires after and verifies or triggers side effects.
2. In the Cloudflare internal AI review incident, what would have prevented the near-miss production YAML modification?
Correct. A PreToolUse hook with path classification was exactly what the Cloudflare team implemented after the incident — checking whether a target path fell within a protected namespace before allowing writes.
The Cloudflare team's own solution was a PreToolUse hook that classified paths before writes, blocking anything targeting production configuration namespaces.
3. A hook command that outputs diagnostic information to stdout but exits 0 — what does Claude Code do with this hook?
Correct. Exit code 0 means pass — the tool call proceeds. Only non-zero exit codes block. Stdout from hooks may be surfaced as context for Claude.
Exit code 0 is a pass — Claude continues with the tool call. Blocking requires a non-zero exit code. stdout content may be surfaced as additional context.
4. What configuration would correctly limit Claude to only reading and writing files within the ./src directory?
Correct. allowedDirectories is the direct configuration for path-level scoping. Setting it to ["./src"] restricts all file operations to that prefix.
allowedDirectories is the correct configuration for path scoping. Setting it to ["./src"] directly restricts file operations to that prefix.
5. The Notion "chainsaw vs scalpel" incident resulted in what specific data loss?
Correct. The agent's "clean up unused imports" task led to running find / -name "*.pyc" -delete, which deleted ML experiment logs on a mounted network share. Recovery took two days.
The agent ran find / -name "*.pyc" -delete, deleting ML experiment logs on a mounted network share. The "clean up unused imports" task had escalated far beyond its intended scope.
6. Which of these is NOT a valid reason to use command-level scoping for the Bash tool?
Correct. Command-level Bash scoping is a security and permission control — it has no effect on the speed of command generation or execution.
Command-level scoping is for access control, not performance. The other three options are valid security use cases for the Bash(pattern) configuration.
7. In the Cursor database migration incident, which architectural control would most directly have prevented the production impact?
Correct. A confirmation gate detecting production connection strings and requiring explicit acknowledgment before proceeding was precisely the control the team's postmortem recommended implementing.
The team's postmortem specifically recommended a confirmation gate that detected production connection strings and required explicit human acknowledgment of the environment before proceeding.
8. What is the "dry run by default, wet run on demand" pattern used by PlanetScale?
Correct. The pattern is a task design choice: Claude always produces a dry-run preview as its first deliverable, and actual execution only begins when the human explicitly instructs it to proceed.
The pattern is a task structure design: Claude produces a preview (dry run) as its first output, and actual execution only begins on explicit human instruction — not a technical dialog, but a designed task phase.
9. How should a confirmation gate hook behave when no human response is received within the timeout window?
Correct. Fail closed is the safe default. On timeout, exit non-zero, block the action, leave state unchanged, and log a clear explanation for the operator.
Fail closed is correct. A timeout that proceeds turns a safety gate into just a delay. The action should be blocked and state left unchanged, with a log message for the returning operator.
10. Why do repeated denied tool attempts constitute a security signal worth investigating?
Correct. Repeated denied attempts signal one of three conditions: the task scope is misconfigured, the instructions are ambiguous (Claude interprets the task differently), or an adversarial prompt injection is attempting constraint escape.
Repeated denied attempts are diagnostic signals — they indicate task scope problems, ambiguous instructions, or adversarial prompt injection attempting to escape your permission constraints.
11. Which file pattern list should a PreToolUse hook on the Read tool check to protect common secrets files?
Correct. The target patterns are files that actually contain credential values: .env variants, key/certificate files, and files with "secret" or "credential" in their names.
The correct patterns target files actually containing credential values: .env variants, PEM/key/certificate files, and files with secret or credential in their names.
12. What makes the twelve-factor app methodology's approach to credentials compatible with Claude Code security?
Correct. Twelve-factor puts credentials in environment variables, not files. Since Claude reads files and not the shell environment directly, code following twelve-factor means Claude encounters only variable names, never actual values.
The compatibility is that twelve-factor credentials live in the environment, not files. Claude reads files — so twelve-factor code means Claude writes references to env vars but never encounters actual credential values.
13. What is the correct hook placement and tool for PostToolUse secret scanning with gitleaks?
Correct. PostToolUse on Write and Edit tools scans each file Claude produces immediately after creation, catching secrets before they enter version control.
Secret scanning belongs as a PostToolUse hook on Write and Edit tools — scanning files right after Claude creates or modifies them, before they can be committed anywhere.
14. In the task-scoped session model, what happens to permission settings when a Claude Code session ends?
Correct. The session model uses a wrapper script that sets task-appropriate permissions for the session, then restores the restrictive default when the session ends — analogous to using role-based access and releasing roles on logout.
The pattern uses wrapper scripts that set task-appropriate permissions at session start and restore the restrictive default at session end, ensuring least-privilege at all times between sessions.
15. What is the primary operational value of a comprehensive audit log of Claude Code tool calls?
Correct. Audit logs enable incident response: when a security event (exposed secret, unintended modification) is discovered, the log provides the timeline, scope, and context needed to assess and respond effectively.
The primary value is incident response capability — when a security event is discovered, the audit log tells you exactly what Claude accessed, when, and with what context, enabling precise scope determination.