feat: initial companion repo for CC-024 Hooks
This commit is contained in:
commit
cd834a7476
7 changed files with 790 additions and 0 deletions
158
exercises/01-block-danger.md
Normal file
158
exercises/01-block-danger.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Exercise 01: Block Dangerous Commands
|
||||
|
||||
**Concept:** Hooks (CC-024)
|
||||
**Level:** Basic
|
||||
**Time:** ~10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Set up a pre-tool-use hook that blocks dangerous shell commands before
|
||||
Claude executes them. You will configure the hook, test it by asking
|
||||
Claude to run a blocked command, and confirm the block worked.
|
||||
|
||||
---
|
||||
|
||||
## Before You Start
|
||||
|
||||
Confirm you have:
|
||||
|
||||
- [ ] Claude Code installed (`claude --version` prints a version)
|
||||
- [ ] This repo cloned and open in Claude Code
|
||||
- [ ] Python 3 available (`python3 --version`)
|
||||
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
**Step 1:** Read the hook script.
|
||||
|
||||
Open `hooks/pre-tool-use.sh` and read through it. Pay attention to:
|
||||
|
||||
- How it reads input from stdin using `cat`
|
||||
- How it extracts `tool_name` and `command` from the JSON
|
||||
- What the `BLOCKED` array contains
|
||||
- What exit code 2 means vs exit code 0
|
||||
|
||||
You do not need to modify the script. Understanding it is the goal of this step.
|
||||
|
||||
**Step 2:** Make the script executable.
|
||||
|
||||
```bash
|
||||
chmod +x hooks/pre-tool-use.sh
|
||||
```
|
||||
|
||||
This is required before Claude Code can run it as a hook.
|
||||
|
||||
**Step 3:** Configure the hook in `.claude/settings.json`.
|
||||
|
||||
Create the file `.claude/settings.json` in this repo with this content:
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ls:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(pwd)",
|
||||
"Bash(date)",
|
||||
"Read",
|
||||
"Write",
|
||||
"Edit",
|
||||
"Glob",
|
||||
"Grep"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(rm -rf *)",
|
||||
"Bash(sudo *)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash hooks/pre-tool-use.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `matcher` field is a regex pattern matched against the tool name.
|
||||
`"Bash"` matches the Bash tool. `".*"` would match all tools.
|
||||
|
||||
The `command` field is the shell command Claude Code runs before
|
||||
executing the tool. It must exit 0 to allow, 2 to block.
|
||||
|
||||
**Step 4:** Start Claude Code in this directory.
|
||||
|
||||
```bash
|
||||
cd claude-code-hooks
|
||||
claude
|
||||
```
|
||||
|
||||
Claude Code reads `.claude/settings.json` on startup and registers the hooks.
|
||||
|
||||
**Step 5:** Test the block by asking Claude to run a dangerous command.
|
||||
|
||||
Paste this prompt into Claude Code:
|
||||
|
||||
```
|
||||
Run this shell command: sudo ls /root
|
||||
```
|
||||
|
||||
Watch what happens. Claude will attempt the Bash tool call. The
|
||||
pre-tool-use hook will run first, match the `sudo` pattern, and exit 2.
|
||||
|
||||
**Step 6:** Verify the result.
|
||||
|
||||
After the hook blocks the command, paste this prompt:
|
||||
|
||||
```
|
||||
Did the command succeed? Why or why not?
|
||||
```
|
||||
|
||||
Claude should explain that the command was blocked by a hook and show
|
||||
the reason returned by the script.
|
||||
|
||||
---
|
||||
|
||||
## Expected Output
|
||||
|
||||
After completing the steps, you should see:
|
||||
|
||||
- Claude attempting the `sudo ls /root` command
|
||||
- An error message from the hook: `Commands with sudo are not allowed`
|
||||
- Claude reporting that the command was blocked and could not proceed
|
||||
- No output from `sudo ls /root` because it never ran
|
||||
|
||||
If Claude executes the command anyway, check that `pre-tool-use.sh` is
|
||||
executable and that `.claude/settings.json` has the correct hook config.
|
||||
|
||||
---
|
||||
|
||||
## What You Learned
|
||||
|
||||
- **PreToolUse hooks intercept before execution.** The command never runs
|
||||
if the hook exits 2. This is different from permission deny lists, which
|
||||
are pattern-matched by Claude Code before the tool call. Hooks let you
|
||||
run arbitrary logic.
|
||||
- **Exit code 2 means block.** Exit 0 means allow. Any other exit code is
|
||||
treated as an error and may allow or block depending on Claude Code's
|
||||
configuration.
|
||||
- **The reason field reaches Claude.** Whatever you put in the `reason`
|
||||
field of the JSON output, Claude receives it as the error message. This
|
||||
lets you explain why a command was blocked in human-readable terms.
|
||||
|
||||
---
|
||||
|
||||
## Next
|
||||
|
||||
Move to [Exercise 02: Build an Audit Trail](./02-audit-trail.md).
|
||||
Loading…
Add table
Add a link
Reference in a new issue