Lesson
Secure Your Claude Skills with Custom PreToolUse Hooks
Use PreToolUse hooks in Claude Code to intercept and validate tool commands, creating a powerful security layer that goes beyond allowed-tools.
- Access
- Included
- Transcript
- Needs source
The allowed-tools setting offers good control, but what if you need to enforce even stricter rules, like allowing only specific script directories or blocking commands that reference sensitive files?
This lesson dives into Claude Code Hooks, specifically the PreToolUse hook, to create a powerful security layer for your AI agent. You'll learn how to write a custom hook in TypeScript, powered by Bun, that intercepts every tool call before it executes—adding an extra layer of protection beyond skill-level restrictions.
How it Works
The PreToolUse hook is configured in your settings.json file. It runs a specified command before any tool is used, passing the full tool call context as JSON to the command's standard input. Your hook script can then inspect this context and decide whether to allow or block the command by using an exit code.
- Exit Code 0: Allow the command to proceed.
- Exit Code 2: Block the command and stop the agent.
This gives you fine-grained, programmatic control over your agent's capabilities.
Workflow Demonstrated
- Setup: Create a hook script and configure it in your
settings.json. - Inspection: Use the
@anthropic-ai/claude-agent-sdkto properly type and inspect the tool call context. - Validation: Write logic to validate commands against specific patterns (e.g., only allowing
bun run scripts/*). - Control: Use exit codes to either allow or block tool execution.
- Refactoring: Leverage Claude Code to refactor existing tools and skills to comply with your new, stricter rules.
By the end of this lesson, you'll have a robust system for securing your AI agent, preventing unwanted commands and ensuring all actions align with your project's security policies.
Key Prompts & Commands
A directory is created and a new bun project is initialized.
mkdir .claude/hooks
cd .claude/hooks
bun init
Install the Claude Agent SDK to get the necessary TypeScript types.
bun i @anthropic-ai/claude-agent-sdk
A prompt to update the hook to enforce a security policy:
@.claude/hooks/index.ts Please update our index.ts to only allow scripts that have been run from the bun run scripts directory. Use regex to check for that pattern. If the condition matches, exit with an exit code of zero. Otherwise, if any other bash tool is called, exit with an exit code of two.
A prompt to make a skill's script cross-platform:
Please update the timestamp script to ensure it works cross-platform between Windows, Linux, and Mac. Don't rely on the system date tool—instead generate a timestamp directly from Bun.
Code Snippets
Configure the PreToolUse hook in your .claude/settings.json to run your script for every tool call:
{
"hooks": {
"PreToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bun run .claude/hooks/index.ts"
}
]
}
]
}
}
The final hook (.claude/hooks/index.ts) validates that only bash commands matching the bun run scripts/* pattern are allowed.
import type { PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
interface BashToolInput {
command: string;
description?: string;
timeout?: number;
run_in_background?: boolean;
}
const input: PreToolUseHookInput = await Bun.stdin.json();
// Only allow Bash commands that run scripts from "bun run scripts" directory
if (input.tool_name === "Bash") {
const toolInput = input.tool_input as BashToolInput;
const command = toolInput.command;
// Block any command that references .env files
if (command.includes(".env")) {
process.exit(2);
}
// Check if the command matches the pattern "bun run scripts/*"
const allowedPattern = /^bun\s+run\s+scripts\//;
if (allowedPattern.test(command)) {
// Allow this command
process.exit(0);
} else {
// Block all other Bash commands
process.exit(2);
}
}
// Allow all non-Bash tools
process.exit(0);