Dùng hook chặn Claude Code đọc file bí mật của bạn
Việc cần làm là giữ Claude Code tránh xa file .env, SSH key và credential của cloud. Một hook PreToolUse nhìn thấy mọi lần gọi tool trước khi nó chạy, và exit code 2 sẽ chặn lệnh đó. Ngày 5 và 6/11/2026 mình test hai bộ guard public, đặt cạnh hai guard của chính mình (một cái chặn đọc file bí mật, một cái chặn lệnh git nguy hiểm), bằng cách cho tất cả chạy cùng một loạt lần gọi tool giả.
Mình test thế nào
Không có secret thật, không đụng home folder thật. Mỗi hook chạy trên một bản copy riêng với HOME giả, mỗi dòng stdin là một JSON event, đúng kiểu Claude Code gọi nó. Mình ghi lại exit code.
printf '{"tool_name":"Read","tool_input":{"file_path":"/work/app/.env"}}' \
| HOME=FAKE_HOME bash COPY_OF_HOOK.sh
24 lần gọi giả được đưa cho guard trong disler/claude-code-hooks-mastery và guard của mình. 33 lần gọi giả đưa cho emaarco/agento-patronum, chạy một lần với hook plugin cũ và một lần với hook hiện tại. Tác giả đã gộp repo này vào emaarco/hogwarts nên URL cũ không còn dùng được. Hai hook cho kết quả y hệt nhau.
Guard của hooks-mastery so với của mình
rm -rf /,rm -Rf ~,sudo rm --recursive --force /: của họ chặn. Của mình để cho permission rule của Claude Code tự lo.rm -rf node_modules: của họ chặn, của mình cho qua.find ~ -deletevàshutil.rmtreecủa Python: cả hai đều cho qua.git push --force: của họ cho qua, của mình chặn.cat .env, Read.env, Read.env.local: cả hai đều chặn.grep -n KEY .env: cả hai đều cho qua (của mình là cố ý).- Read
.env.examplevà Readsrc/.environment.ts: của họ chặn, của mình cho qua. cat ~/.zshrc,cat ~/.aws/credentials,cat ~/.ssh/id_ed25519: của họ cho qua, của mình chặn.
Không guard nào phủ hết. Của họ mạnh hơn ở mấy lệnh xoá, của mình mạnh hơn ở chuyện đọc credential qua shell.
agento-patronum: chặn file tốt, chặn shell yếu
Nó có sẵn 12 rule: .env, .env.*, *.pem, *.key, ~/.ssh/*, ~/.aws/credentials, ~/.aws/config, ~/.docker/config.json, ~/.kube/config, ~/.npmrc, ~/.pypirc và lệnh printenv. Bản cập nhật ngày 10/11 thêm năm rule nữa: ~/.config/gh/hosts.yml, ~/.config/glab-cli/config.yml, ~/.git-credentials, ~/.netrc và ~/.claude/.credentials.json.
Ngày 14/11/2026 mình chạy lại các lần gọi giả vào hook đã cập nhật đó, trong một sandbox macOS: không có mạng, chỉ được ghi vào hai folder tạm, HOME giả, môi trường trống và giới hạn 10 giây CPU. Mình đọc hết 163 dòng của hook trước khi chạy. Danh sách 33 lần gọi ban đầu không được lưu lại, nên mình dựng lại từ ghi chú (32 lần gọi cộng 3 lần mình ghi riêng, nên không phải đúng y bộ cũ). Mọi kết quả bên dưới đều giữ nguyên, trừ chỗ có nói khác.
- Tool đọc ghi file bị chặn tốt. Read, Write, Edit và MultiEdit trên
.env,.env.production.bak,server.pem,~/.ssh/id_rsa, một file lồng sâu~/.ssh/keys/deploy, thậm chí cả~/.ssh/../.ssh/id_rsa, đều exit 2. - Shell thì để ngỏ.
cat ~/.ssh/id_rsa,cat .env,grep KEY .envvàcat ~/.aws/credentialsđều đi qua. Rule shell duy nhất chỉ xem lệnh có bắt đầu bằngprintenvkhông, nênenv,sudo printenvvàcd /tmp && printenvcũng qua luôn. Docs của tác giả cũng nói thẳng chuyện này: "it won't catch commands buried in pipes or subshells". - Có hai tool chẳng hề bị kiểm. Hook chỉ nghe Read, Write, Edit, MultiEdit và Bash, nên Grep vào một path
.envhay Glob vào~/.ssh/*đi thẳng qua. - Danh sách cố định thì vừa sót vừa chặn nhầm.
.envrc, và một private key copy ra ngoài~/.sshđều đi qua. File token của GitHub CLI đi qua trong lần chạy đầu của mình, nhưng từ bản cập nhật ngày 10/11 thì bị chặn. Pattern*lại vượt qua ranh giới folder, nên.env.example,i18n/menu.keyvà~/.ssh/known_hostsbị chặn. - Nó chặn hết khi có lỗi, trừ khi input hỏng. File rule hỏng hoặc
HOMEchưa set là mọi lệnh đều bị chặn. Mặc định như vậy là đúng. Nhưng input không phải JSON hợp lệ, như văn bản thường hay một event bị cụt, làmjqlỗi và hook exit 5, Claude Code hiểu đó là hook lỗi chứ không phải chặn. Input rỗng thì exit 0. Event bị cụt mình gửi có nhắc tới một file.envmà vẫn lọt qua. - Log ghi nhiều dòng. Mỗi lần chặn ghi một object JSON thụt lề dài sáu dòng, vì
jqchạy không có-c. Cả 21 lần gọi bị chặn trong lần chạy lại đều ghi đúng sáu dòng.
Với shell thì cách nào ổn, và phải trả giá gì
Bạn so trên toàn bộ text của lệnh, đừng chỉ nhìn chữ đầu tiên. Guard git của mình chạy regex trên cả câu lệnh, nên bắt được cd x && git push, thứ mà rule chỉ xét phần đầu sẽ bỏ sót. Cái giá là báo nhầm. Trong lúc test, guard của mình chặn một lệnh chỉ có chữ "git push" nằm trong một chuỗi trong ngoặc, và chặn một lệnh khác chỉ vì nhắc tới .env trong một cái nhãn. Guard của hooks-mastery cũng dính đúng kiểu báo nhầm đó.
Nếu bạn tự viết
- Cho Grep và Glob vào matcher, đừng chỉ có Read, Edit và Bash.
- Với Bash, so trên toàn bộ text của lệnh, và chấp nhận đôi lúc báo nhầm.
- Thiếu file rule, file rule hỏng, hoặc input không phải JSON hợp lệ thì chặn hết.
- Mỗi lần chặn đều kèm lý do mà người đọc hiểu được, và ghi log mỗi lần thành đúng một dòng.
- Test bằng lần gọi giả như ở trên, và xem exit code.
Hook không thay được deny rule trong settings. Chính README mới của patronum có ghi là deny rule cho Read còn được đưa vào sandbox của Claude Code. Bạn dùng cả hai.
English