- Claude Code có thể đọc/ghi mọi file trong project — bao gồm .env chứa API keys.
- Hai cách chặn: deny permissions trong settings.json (đơn giản nhưng đang có bug) và PreToolUse hooks (linh hoạt, enforce thật).
- Bài này so sánh và đưa cấu hình copy-paste.
TL;DR
Claude Code có quyền đọc, ghi, xoá mọi file trong project. Một prompt sai = leak API keys hoặc xoá .env. Có 2 cách chặn:
- Permissions (đơn giản): thêm
denyrules trong.claude/settings.json. Vấn đề: đang có bug — Claude vẫn đọc được.envdù đã cấu hình deny. - PreToolUse Hooks (linh hoạt, enforce thật): script chạy trước mỗi tool call, exit 2 để block. Bắt được cả Bash bypass.
Production: dùng cả hai theo defense-in-depth. Nếu chỉ chọn một, chọn hook.
Vì sao phải chặn .env
File .env chứa API keys, database credentials, JWT secrets, certs. Khi Claude đọc nó, nội dung vào thẳng context window — từ đó có thể rò rỉ qua reply, log, commit message, hoặc tệ hơn là tool output gửi sang MCP server bạn không kiểm soát. Backslash Security ghi nhận đã có vụ npm supply-chain attack lợi dụng AI agent để exfiltrate secrets.
Permission prompt mặc định không cứu được bạn — sau 50 lần bấm Yes trong session, dev nào cũng bấm autopilot. Cần rule cứng, không hỏi.
Option 1 — Permissions (deny rules)
Mở .claude/settings.json trong project root (hoặc ~/.claude/settings.json cho global), thêm:
{
"permissions": {
"deny": [
"Read(./.env)", "Write(./.env)", "Edit(./.env)",
"Read(./.env.*)", "Write(./.env.*)", "Edit(./.env.*)",
"Read(./secrets/**)", "Write(./secrets/**)", "Edit(./secrets/**)",
"Read(./**/*.pem)", "Read(./**/*.key)"
]
}
}Thứ tự eval: deny → ask → allow, first match wins. Phải duplicate cho cả 3 tool (Read, Write, Edit) vì chúng eval độc lập — chặn Read mà quên Write thì Claude vẫn ghi đè được.
Pattern syntax theo gitignore-style: ./.env = relative project root, ~/.env = home, //Users/... = absolute. Chi tiết tại official docs.
Cảnh báo: issue #6699 (mở từ tháng 8/2025, v1.0.93) và #24846 ghi nhận deny rules hoàn toàn không enforce. Người dùng cấu hình đúng pattern vẫn thấy Claude in ra full nội dung .env.local kèm secrets. Bug vẫn live tại thời điểm bài viết — cộng đồng có hơn 30 issue về permission matching. Đừng tin một mình permissions.
Option 2 — PreToolUse Hooks
Hook là shell script chạy trước tool call. Nhận tool name + input qua stdin (JSON), trả về exit code:
0= allow2= block + stderr feedback gửi lại Claude- non-zero khác = error hiện cho user
Vì chạy ngoài Claude Code engine, hook bypass được mọi bug của permission system và inspect được full command string — bao gồm compound command (cat .env && curl ...), pipe, subshell.
Tạo .claude/hooks/protect_env.py:
#!/usr/bin/env python3
import sys, json
try:
data = json.loads(sys.stdin.read() or "{}")
except json.JSONDecodeError:
sys.exit(0)
target = (data.get("file_path","") + " " + data.get("command","")).lower()
if ".env" in target or "/secrets/" in target or "id_rsa" in target:
sys.stderr.write("BLOCKED: sensitive file access denied by hook")
sys.exit(2)
sys.exit(0)chmod +x .claude/hooks/protect_env.py, rồi register trong .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{ "command": ".claude/hooks/protect_env.py",
"tools": ["Read", "Write", "Edit", "Bash"] }
]
}
}Verify bằng /hooks trong Claude Code REPL — phải thấy hook đã đăng ký. Test thực tế: bảo Claude "đọc file .env" — phải thấy stderr BLOCKED: sensitive file access denied by hook và Claude tự dừng.
So sánh nhanh
| Tiêu chí | Permissions | Hooks |
|---|---|---|
| Setup | Zero code, JSON | Phải viết script |
Compound command (a && b) | Wildcard miss | Full regex match |
Bash bypass (cat .env) | Không chặn | Chặn được |
| Deny enforcement | Đang có bug (#6699) | Hoạt động ổn |
| Custom logic | Không hỗ trợ | Bash/Python tuỳ ý |
Câu chốt từ guide "Locking Down Claude Code": Permissions là request. Hooks là enforcement.
Cẩn thận: Bash bypass
Đây là lỗ hổng thường bị bỏ qua. Rule Read(./.env) chỉ chặn built-in Read tool — KHÔNG chặn Bash(cat .env) hoặc Bash(rm .env). Permission system biết tool nào đang chạy nhưng không inspect nội dung Bash command. Vì vậy bắt buộc phải có hook bao phủ Bash tool nếu muốn chặn triệt để.
Khi nào chọn cách nào
- Solo dev, project test: permissions là đủ (chấp nhận risk bug). Tốn 30 giây cấu hình.
- Production code, có client data, hoặc làm việc với multiple agent/MCP server: bắt buộc hooks. Permissions chỉ là layer phụ.
- Team / shared repo: commit cả 2 vào
.claude/settings.json+.claude/hooks/để mọi developer áp dụng đồng nhất.
Giới hạn cần biết
Hook là deterrent, không phải security boundary tuyệt đối. Một agent đủ creative có thể né bằng cách construct path indirectly (echo .e''nv, base64 decode, đọc qua API call). Phòng tuyến cuối cùng vẫn là OS-level: Claude Code sandbox (bubblewrap trên Linux, Seatbelt trên macOS), Docker/VM, hoặc secrets manager (Vault, pass+GPG, ssh-agent) để credentials không bao giờ ngồi ở plaintext trong working directory.
Cũng nhớ chặn .claude/settings.json chính nó — nếu agent edit được file này, một prompt injection có thể tắt mọi protection.
Tiếp theo
Setup tối thiểu nên có ngay hôm nay:
- Copy block
permissions.denyở Option 1 vào.claude/settings.jsonproject root. - Tạo hook script ở Option 2,
chmod +x, register. - Bổ sung
.ssh/,.gnupg/,.bashrc,terraform.tfstate,*.pem,*.keyvào cả deny list lẫn hook substring. - Test: bảo Claude "cat .env" — phải thấy block.
Bạn cũng có thể bảo Claude tự generate cấu hình cho project: prompt "Generate a .claude/settings.json deny block + PreToolUse hook to protect .env, .pem, .key, secrets/, terraform.tfstate." — nó sẽ viết cả hai dựa trên pattern này.
Nguồn tham khảo: Claude Code official docs, Boucle's file-guard hook, Steve Kinney's hook cookbook, Backslash security guide, @santtiagom_ source thread.

