Deny writes to protected files (.env/secrets/keys) and dangerous Bash patterns, with a toast explaining why

hooks/register.tsx 35 lines1import type { Register } from 'claude-code'
2
3// 敏感路径:.env / .env.*、secrets/、PEM 私钥、SSH 私钥。
4const PROTECTED = /(^|\/)\.env(\.|$)|(^|\/)secrets(\/|$)|\.pem$|(^|\/)id_(rsa|ed25519)(\.|$)/
5
6// 高危 Bash 模式:递归强删根/家目录、force push、格式化/整盘写、fork 炸弹。
7const DANGEROUS =
8 /(^|\s)rm\s+-r[f]*\s+(\/|~|\$HOME)(\s|$)|git\s+push\s+([^&|;]*\s(-f|--force)\b)|mkfs|dd\s+if=|:\(\)\s*\{\s*:\|:\s*&\s*\}\s*;/
9
10function deny($: { plugin: { name: string }; ui: { toast(t: string): void } }, reason: string) {
11 const text = `${$.plugin.name}: ${reason}`
12 $.ui.toast(text)
13 return { deny: text }
14}
15
16export const register: Register = on => {
17 on('tool.call', { tool: 'Edit' }, ($, e, next) =>
18 PROTECTED.test(e.file_path)
19 ? deny($, `已拦截对 ${e.file_path} 的编辑(敏感文件受保护)`)
20 : next(e),
21 )
22
23 on('tool.call', { tool: 'Write' }, ($, e, next) =>
24 PROTECTED.test(e.file_path)
25 ? deny($, `已拦截对 ${e.file_path} 的写入(敏感文件受保护)`)
26 : next(e),
27 )
28
29 on('tool.call', { tool: 'Bash' }, ($, e, next) =>
30 DANGEROUS.test(e.command)
31 ? deny($, `已拦截高危命令:${e.command.slice(0, 80)}`)
32 : next(e),
33 )
34}
35