Trong dev, .env chứa:
- Cài đặt end-to-end một hook PreToolUse block Claude đọc file .env
- Viết Node.js script hook đầy đủ với error handling
- Config hook trong .claude/settings.local.json
- Test hook với 4 scenario khác nhau (Read direct, Grep, gián tiếp, false positive)
- Debug khi hook không trigger
Mở đầu: Tại sao .env là priority #1 để protect?
- API key (Anthropic, Stripe, AWS)
- DB password
- JWT secret
- Third-party credential
Nếu Claude đọc .env:
- Token sensitive có thể lọt vào transcript → shared inadvertently
- Claude có thể quote secret trong response → tiếp tục lan truyền
- Compromise security khi transcript được lưu hoặc share
Quy tắc vàng: .env tuyệt đối không xuất hiện trong prompt/response của LLM. Hook là cách enforce tuyệt đối.
Thiết kế (nhắc lại từ Bài 4.13)
Bước 1: Pre? → Yes, cần block trước khi tool đọc.
Bước 2: Matcher? → Read|Grep (2 tool có thể truy cập file content).
Bước 3: Logic:
Đọc stdin JSON
Lấy file_path (Read) hoặc path (Grep)
Match pattern /\.env(\.|$)/?
Yes → exit 2 + stderr
No → exit 0
Bước 4: Exit code:
- Block → 2
- Allow → 0
Bước 1: Cài structure
Trong project root:
mkdir -p .claude/hooks
Directory layout sau khi xong
project/
├── .claude/
│ ├── hooks/
│ │ └── read_hook.js ← script hook
│ └── settings.local.json ← config
├── src/
├── package.json
└── ...
Bước 2: Viết script read_hook.js
File: .claude/hooks/read_hook.js
#!/usr/bin/env node
/**
* PreToolUse hook: Block Claude from reading .env files.
* Matchers this runs on: Read, Grep
*/
async function main() {
// 1. Đọc toàn bộ stdin (JSON do Claude Code push)
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
let input;
try {
input = JSON.parse(Buffer.concat(chunks).toString());
} catch (err) {
console.error("[read_hook] Invalid JSON from stdin:", err.message);
process.exit(1); // hook error, không block
}
// 2. Extract file path (defensive)
const path =
input.tool_input?.file_path ||
input.tool_input?.path ||
'';
// 3. Match .env pattern
// Match: .env, .env.local, .env.production, .env.test, etc.
// Không match: environment.ts, env-config.ts
const ENV_PATTERN = /(^|\/)\.env(\.[a-zA-Z0-9_-]+)?$/;
if (ENV_PATTERN.test(path)) {
console.error(
`[read_hook] Blocked access to env file: ${path}\n` +
`Use process.env at runtime instead.`
);
process.exit(2); // BLOCK
}
// 4. Allow
process.exit(0);
}
main().catch(err => {
console.error("[read_hook] Unexpected error:", err);
process.exit(1);
});
Phân tích script
- Shebang
#!/usr/bin/env node— cho phép chạy trực tiếp nếu bạnchmod +x, nhưng config dưới sẽ gọinode ... - Stdin reading — loop
for await ... chunks, tập trung buffer - Parse defensive — try/catch, fall back exit 1 nếu JSON invalid
- Path extraction — optional chaining cho cả
file_path(Read/Edit) vàpath(Grep) - Regex tight — match
.envtheo đầu dòng hoặc sau/, optional suffix - Exit codes — 2 block, 0 allow, 1 error
Bước 3: Config settings.local.json
File: .claude/settings.local.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Grep",
"hooks": [
{
"type": "command",
"command": "node /absolute/path/to/project/.claude/hooks/read_hook.js"
}
]
}
]
}
}
Absolute path! Dùng đường dẫn tuyệt đối, không ./hooks/.... Lý do security (bị đề cập ở Bài 4.15).
Ví dụ:
- macOS:
/Users/john/Projects/uigen/.claude/hooks/read_hook.js - Linux:
/home/john/projects/uigen/.claude/hooks/read_hook.js - Windows WSL:
/mnt/c/Users/john/projects/uigen/.claude/hooks/read_hook.js
Tại sao settings.local.json?
settings.local.jsonkhông commit (.gitignoreed mặc định)- Path tuyệt đối của bạn khác path teammate → commit sẽ gây conflict
- Bài 4.15 dạy cách share hook với team (dùng
$PWD+ init script)
Bước 4: Restart Claude Code
Claude Code đọc settings chỉ lúc start. Phải restart:
/exit
claude
Bước 5: Test với 4 scenario
Scenario 1: Đọc .env trực tiếp
Prompt:
Đọc file .env của project này cho tôi xem.
Expected:
[Claude attempts] Read tool với file_path=".env"
[Hook triggers] exit 2, stderr: "Blocked access to env file: .env"
[Claude receives error] Claude giải thích không thể đọc do hook block
Claude sẽ respond kiểu:
Tôi không thể đọc file
.env— có một PreToolUse hook trong project block access đến env files. Đây là protection cho credentials. Nếu bạn cần verify env var runtime, dùngprocess.env.XXXthay vì đọc file.
✅ Pass.
Scenario 2: Grep trong .env
Prompt:
Search xem file .env có chứa "ANTHROPIC" không.
Expected: Grep tool bị block (hook match cả Read|Grep).
✅ Pass.
Scenario 3: Đọc file khác (không phải .env)
Prompt:
Đọc package.json của project này.
Expected: Hook cho phép (package.json không match pattern).
Claude đọc bình thường, trả về nội dung.
✅ Pass.
Scenario 4: File có tên gần giống (false positive test)
Prompt:
Đọc file environment.ts trong src/config/ (tôi biết file này public).
Expected: Hook cho phép (environment.ts không match /(^|\/)\.env(\.|$)/).
✅ Pass — regex đủ tight.
Scenario 5: Đọc .env.local, .env.production
Prompt:
Check xem .env.production có config NODE_ENV không.
Expected: Hook block — pattern match .env.production.
✅ Pass.
Debug khi hook không hoạt động
Triệu chứng 1: Hook không trigger
Claude đọc .env bình thường, không có error.
Check:
- Đã restart Claude Code sau edit settings? →
/exit && claude - File
settings.local.jsonđúng path? Check:
cat .claude/settings.local.json
- Path absolute đúng? Test trực tiếp:
echo '{"tool_input":{"file_path":".env"}}' | node /abs/path/read_hook.js
echo "Exit: $?"
Expected: Exit: 2
- Matcher syntax đúng? (dùng
Read|Grep, không phải["Read", "Grep"])
Triệu chứng 2: Hook error, không block
Claude vẫn đọc .env, log có [read_hook] error.
Check:
- Script có lỗi syntax JS? Chạy:
node read_hook.jsvới input manual - Node version đủ mới? (cần ≥14 cho
for await) - Permission file hook?
chmod 755 read_hook.js(nhiều khi không cần vì config gọinode)
Triệu chứng 3: Hook chạy nhưng không block
Exit code đúng là 2 nhưng Claude vẫn đọc?
Check:
- Confirm đây là PreToolUse, không nhầm PostToolUse
tool_nametrong JSON match matcher? Log input ra file để verify- Có hook khác override ở file settings khác?
Tactic: Log debug temporary
Thêm tạm vào hook:
require('fs').appendFileSync(
'/tmp/hook-debug.log',
JSON.stringify(input) + '\n'
);
Trigger prompt → xem /tmp/hook-debug.log. Biết chính xác hook nhận gì.
Xóa dòng này sau khi debug xong.
Mở rộng: Block thêm folder secrets/
Bổ sung pattern:
const BLOCKED_PATTERNS = [
/(^|\/)\.env(\.[a-zA-Z0-9_-]+)?$/, // .env files
/\/secrets\//, // secrets/ folder
/\/credentials\.json$/, // credentials.json
/\.key$/, // *.key (SSH keys, etc.)
/\.pem$/, // *.pem cert
];
for (const pat of BLOCKED_PATTERNS) {
if (pat.test(path)) {
console.error(`[read_hook] Blocked access to sensitive file: ${path}`);
process.exit(2);
}
}
Hook giờ block phong phú hơn. Cùng mục đích: không cho credential vào prompt.
Ví dụ thực chiến: Team rollout
Tuần 1: Pilot 1 dev
Dev A setup hook trên máy mình. Dùng 1 tuần, verify:
- Không false positive quá nhiều?
- Có case hook miss?
- Dev workflow bị ảnh hưởng không?
Tuần 2: Rollout team
Viết doc internal:
1. Copy hooks/ từ repo template
2. Run ./scripts/init-claude.sh
3. Restart Claude Code
4. Verify: bạn không đọc được .env
init-claude.sh tự generate settings.local.json với path đúng cho máy từng dev (xem Bài 4.15).
Tuần 3: Monitor + iterate
Mỗi dev ghi log file hooks trigger — review cuối tuần:
- Pattern nào chặn đúng đắn
- False positive nào
- Bổ sung file/folder cần block
Kết quả
- 100% dev có hook active
- Zero incident
.envcontent leak - Dev confidence dùng Claude Code tự nhiên, không lo
Case studies theo ngành
Bank — Block prod DB read
Setup: PreToolUse hook trên Bash → regex match SELECT/pg_dump targeting prod host.
- Kết quả: Zero incident prod data access trong tool.
Health — Block PHI folder
Setup: PreToolUse trên Read/Grep → block /patients/, /ehr-exports/.
- Kết quả: HIPAA audit pass, tool usage scaled from 5 → 30 engineer safely.
University — Block student data
Setup: Block /student-records/, /grades/.
- Kết quả: Compliance FERPA, tool approved for instructor use.
Enterprise — Block IP protect
Setup: Block /proprietary-algorithms/, custom copyrighted codebases.
- Kết quả: Legal approval để dev dùng Claude Code trên IP-sensitive repo.
Defense — Air-gap verify
Setup: Trong addition to hook, PreToolUse verify network egress patterns.
- Kết quả: Deploy Claude Code + Bedrock trong classified env.
Anti-patterns
Dùng path tương đối
Settings:
"command": "node ./hooks/read_hook.js"
- Hậu quả
- Hook chỉ chạy nếu Claude Code start từ project root. Chạy từ sub-dir → path fail.
- Cách đúng
- Absolute path, hoặc
$PWD/...với setup script (Bài 4.15).
Pattern block quá rộng
if (path.includes('env')) { exit 2 }
- Hậu quả
- Block luôn
environment.ts,event-handler.ts(có "env"). - Cách đúng
- Regex tight
^\.envhoặc(^|\/)\.env(\.|$)/.
Không handle exception
const input = JSON.parse(stdin); // crash if invalid
- Hậu quả
- Hook crash, Claude continue. Security gap.
- Cách đúng
- Try/catch, exit code phù hợp.
Hook depend vào env variable không set trong session
const PROJECT_ROOT = process.env.PROJECT_ROOT; // undefined
Cách đúng: Hard code hoặc resolve từ file path.
Dùng settings.json (shared) cho absolute path cá nhân
- Hậu quả
- Commit vào Git → teammate pull, path sai.
- Cách đúng
- Absolute path trong
settings.local.json(không commit). Hoặc dùng$PWDtrick Bài 4.15.
Mẹo nâng cao
Mẹo 1: Config bằng ENV var
Cho phép override pattern qua env:
const customPatterns = (process.env.CLAUDE_HOOK_BLOCK_PATTERNS || '')
.split(',')
.filter(Boolean)
.map(p => new RegExp(p));
Dev có use case riêng → set env var, không phải edit script.
Mẹo 2: Hook viết bằng shell cho đơn giản
#!/bin/bash
read -r -d '' input
path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if echo "$path" | grep -qE '(^|/)\.env(\.|$)'; then
echo "Blocked: $path" >&2
exit 2
fi
exit 0
Ngắn hơn Node, đủ cho simple case.
Mẹo 3: Multiple hook chain
Có thể có nhiều hook cho cùng matcher:
"PreToolUse": [
{ "matcher": "Read", "hooks": [{...}, {...}] }
]
Chạy tuần tự. Nếu hook đầu exit 2 → stop, không chạy hook tiếp.
Mẹo 4: Hook conditional bằng file glob
Hook chỉ active trong một số folder:
if (!path.startsWith(process.cwd() + '/src/')) {
process.exit(0); // not in src, allow
}
// ... strict check cho src
Áp dụng ngay
Bài tập 1: Cài hook .env (20 phút)
- Bước 1
- Tạo script đúng như trên.
- Bước 2
- Config
settings.local.jsonvới absolute path máy bạn. - Bước 3
- Restart Claude Code.
- Bước 4
- Test 5 scenario:
- Read
.env→ Block? Có Không - Grep
.env→ Block? Có Không - Read
package.json→ Allow? Có Không - Read
environment.ts→ Allow? Có Không - Read
.env.production→ Block? Có Không
Nếu 5/5 → hook chuẩn.
Bài tập 2 (optional, 15 phút): Extend block list
Thêm pattern cho:
.key,.pem(SSH/cert)- Folder
secrets/,credentials/ - File
.htpasswd - Any file matching
id_rsa*
Test với prompt yêu cầu đọc mỗi loại. Verify block đúng.
Tóm tắt bài học
- Implement hook = script shell + config JSON — không phức tạp sau khi setup lần đầu.
- Absolute path là yêu cầu bảo mật — đừng dùng relative.
- Test nhiều scenario — true positive, true negative, false positive.
- Debug bằng log JSON vào /tmp — khi hook không làm như mong đợi.
- Exit 2 = block + stderr message — Claude nhận message, giải thích cho user.
Tài liệu tham khảo
- Claude Code hooks examples
- MITRE ATT&CK T1574.007 — Path interception
- OWASP Binary Planting
- Bài 4.15 — Gotchas + absolute path trick