Claude Code Hooks. Guardrails của agent không chỉ là prompt.
Một coding agent không chỉ trả lời bằng chữ. Nó có thể chạy Bash, sửa file, gọi MCP tool và đẩy thay đổi vào repo thật. Claude Code Hooks đặt điểm kiểm soát quanh Tool use: đọc JSON đầu vào, match đúng event, rồi cho chạy tiếp, hỏi lại, hoặc chặn. Bài viết giải thích vì sao guardrail cho AI engineer phải chạy ở runtime, có log, có test, và được review như production code.
Agent không chỉ viết chữ. Agent chạm vào máy thật
Bạn giao Claude Code sửa một module deploy. Nó đọc repo, tạo plan, rồi chuẩn bị chạy rm -rf /tmp/build để dọn build cũ. Nếu lệnh đúng thư mục, không sao. Nếu biến path bị rỗng, hoặc prompt injection trong file log làm agent đổi mục tiêu, bạn không muốn phát hiện chuyện đó sau khi tool đã chạy.
Vì vậy guardrails engineering cho coding agent không thể dừng ở system prompt. Prompt là lời dặn. Hook là điểm kiểm tra ở runtime, nằm đúng chỗ agent sắp gọi tool. Nó nhìn thấy Tool use đã được model tạo ra, đọc JSON đầu vào, rồi cho đi tiếp, hỏi lại, hoặc chặn.
ý định bằng chữ
tham số cụ thể
policy runtime
filesystem, shell, MCP
Một hook là cổng kiểm soát trong lifecycle
Theo tài liệu Claude Code, hooks là các handler do người dùng định nghĩa. Handler có thể là command, HTTP endpoint, MCP tool, prompt, hoặc agent. Chúng chạy tự động tại các event của Claude Code: bắt đầu session, nhận prompt, trước tool, sau tool, trước khi stop, trước khi compact, và vài điểm khác trong vòng đời agent.
Với guardrails engineering, event quan trọng nhất thường là PreToolUse. Nó chạy sau khi Claude đã tạo tham số cho tool, nhưng trước khi tool thực thi. Nghĩa là hook thấy đủ chi tiết để kiểm policy, nhưng vẫn còn kịp chặn tác động.
nạp ngữ cảnh
kiểm prompt
chặn trước tool
blockableduyệt quyền
log sau tool
ngắt vòng lặp
kiểm hoàn tất
Từ Tool use sang quyết định deny
Command hook nhận JSON qua stdin. Với Bash, JSON có thể chứa tool_name: "Bash" và tool_input.command. Với Write hoặc Edit, nó chứa file_path và nội dung liên quan. Đây là dữ liệu mà policy script cần, không phải một đoạn chat đã bị diễn giải lại.
Có hai cách ra quyết định. Cách nhanh: in lý do ra stderr và exit 2. Với PreToolUse, điều này block Tool use. Cách có cấu trúc hơn: exit 0 và in một JSON có hookSpecificOutput.permissionDecision, ví dụ deny, ask, hoặc allow. Không trộn hai kiểu này, vì Claude Code chỉ đọc JSON khi hook thoát bằng code 0.
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build"
}
}Chỉ dùng khi policy đã kiểm đủ tool, command, path và cwd.
Có thể block nhanh bằng stderr + exit 2 hoặc trả JSON deny.
Dùng khi hook thấy rủi ro nhưng cần user hoặc policy khác quyết định.
Log đủ để audit, nhưng phải tránh lộ secret trong input.
Policy tốt là policy hẹp, test được, có log
Một hook tốt không cố đoán ý định của model bằng cảm tính. Nó kiểm các điều kiện rõ: tool nào, file nào, command nào, path nào, mode nào, và có bằng chứng gì trong repo. Càng gần filesystem và shell, policy càng nên deterministic.
Đây là chỗ hook khác với moderation chung. Moderation thường hỏi: nội dung này có an toàn không. Runtime hook hỏi câu hẹp hơn: thao tác này, với input này, tại thư mục này, có được phép chạy ngay bây giờ không.
Dễ bị quên khi context dài, Tool use phức tạp, hoặc prompt injection nằm trong file.
Policy nhìn input thật, trả lý do rõ, và để lại audit trail cho team.
Những hook đáng viết đầu tiên
Nếu một team mới bắt đầu dùng Claude Code cho repo thật, tôi sẽ không viết hook quá thông minh ngay. Bắt đầu bằng những cổng đơn giản nhưng có giá trị cao: chặn protected paths, chặn command nguy hiểm, quét secret trước khi Write, ép test sau Edit, và chặn MCP tool ghi dữ liệu ngoài allowlist.
Matcher giúp giảm nhiễu: Bash chỉ match tool Bash, còn Edit|Write match chính xác hai tool Edit và Write. Với MCP tool, tên thường có dạng mcp__server__tool, nên muốn match mọi tool từ một server cần pattern kiểu mcp__memory__.*, không phải chỉ mcp__memory.
chặn .env, credentials, prompts prod
deny rm *, curl | sh, sudo ngoài allowlist
giới hạn domain khi xử lý dữ liệu nhạy cảm
chặn write tool ngoài server đã duyệt
chạy test, lint, typecheck sau thay đổi
không cho dừng khi checklist chưa xong
Không phải hook nào cũng chặn được
PostToolUse chạy sau khi tool đã thành công. Nó hữu ích để log, chạy lint, kiểm diff, hoặc yêu cầu agent sửa lỗi sau khi test fail. Nhưng nó không thể đảo ngược việc file vừa bị ghi. Async hook còn rõ hơn: nó chạy nền, Claude tiếp tục làm việc, nên không dùng để block.
Điểm nguy hiểm khác nằm trong chính hook. Command hook chạy với quyền user hệ thống. Nếu script hook xử lý path không chặt, không quote biến shell, hoặc tin dữ liệu JSON mù quáng, guardrail sẽ biến thành bề mặt tấn công mới.
Guardrail phải sống cùng repo, không sống trong trí nhớ
Claude Code cho phép đặt hook ở user settings, project settings, local settings, managed policy, plugin, skill, hoặc agent frontmatter. Với team, phần dùng chung nên nằm trong project hoặc managed policy, còn thử nghiệm cá nhân nằm ở local settings. Đừng để policy quan trọng chỉ tồn tại trong một máy developer.
Mapping rộng hơn cũng khá rõ. Trong OWASP Top 10 for LLM Applications v1.1, prompt injection, insecure output handling, sensitive information disclosure, insecure plugin design, và excessive agency đều là rủi ro lớn của LLM app. NIST AI RMF thì đặt trọng tâm vào quản trị, đo lường, và quản lý rủi ro. Hooks không thay thế hai khung đó, nhưng biến một phần policy thành code chạy được trong workflow hằng ngày.
.claude/settings.local.jsonthử nghiệm cá nhân
.claude/settings.jsonpolicy chung của repo
hooks/hooks.jsonđóng gói toolchain
policy settingschuẩn tổ chức
Công ty lớn không đặt một cổng. Họ xếp nhiều lớp guardrail
Hook bạn vừa viết canh một máy, một repo. Nhưng một hệ thống AI phục vụ hàng triệu người dùng, ví dụ chatbot hỗ trợ, copilot trong sản phẩm, hay agent tự động gọi API, thì không thể tin vào một checkpoint duy nhất. Vì vậy các công ty lớn dựng guardrail theo kiểu defense in depth: nhiều lớp xếp nối nhau, lớp sau bắt cái lớp trước bỏ lọt.
Các lớp đó rơi vào ba nhóm. Input guardrail chạy trước khi model đọc prompt: lọc nội dung độc hại, phát hiện prompt injection và jailbreak, che thông tin nhạy cảm như PII. Output guardrail chạy trước khi câu trả lời đến tay người dùng hoặc hệ thống tiếp theo: kiểm nội dung, validate đúng schema, đối chiếu câu trả lời với nguồn để bắt hallucination, và redact dữ liệu lộ ra. Action guardrail chạy ngay trước khi một tool thực thi. Đây chính là ý tưởng của PreToolUse hook, nhưng ở quy mô service và thường kèm một người duyệt cho thao tác rủi ro cao.
Điểm chung là họ coi guardrail như production code thật. Policy được version trong repo, có eval suite chạy thử trên tập case đã gán nhãn, có red team tấn công thử để tìm lỗ hổng, và có observability để theo dõi khi đã chạy thật: log, trace, metric, và một đường báo sự cố khi guardrail chặn nhầm hoặc bỏ lọt. Vài bộ công cụ phổ biến cho hướng này là Llama Guard, NeMo Guardrails, OpenAI Moderation API, Guardrails AI và Azure AI Content Safety.
Đủ cho một repo, một developer. Nhưng chỉ canh đúng điểm gọi tool, không thấy input độc hại hay output sai.
Input, output và action mỗi lớp một việc. Lớp sau bắt cái lớp trước bỏ lọt, và mọi lớp đều có log để audit.
moderation, chặn prompt injection, lọc PII
LLM sinh câu trả lời hoặc tool call
kiểm nội dung, validate schema, đối chiếu nguồn
gate tool, người duyệt cho thao tác rủi ro cao
Cách viết hook không tự làm hệ thống yếu đi
Một guardrail hook tốt nên trả lời được bốn câu hỏi: nó bảo vệ tài sản nào, nó match đúng event nào, nó block dựa trên dữ liệu nào, và người đọc log có hiểu vì sao bị block không. Nếu không trả lời được, hook đó thường là noise.
Nguồn nền cho bài viết: Claude Code Hooks reference, Automate workflows with hooks, OWASP Top 10 for LLM Applications, và NIST AI RMF.
Hook bảo vệ asset nào, ở event nào?
Narrow tool name trước, regex sau.
Parse JSON bằng thư viện, không grep chuỗi thô.
Deny có lý do rõ cho Claude hoặc user.
Log đủ để review nhưng không lộ secret.
Có fixture cho command nguy hiểm và false positive.