Claude Code mod: blocks Claude from reading .env secret files and counts blocked attempts in the status line

A Claude Code mod (v2.1.287+) that stops Claude from reading your .env secret files.
Mod لـ Claude Code يمنع Claude من قراءة ملفات .env (مفاتيحك السرية)، ويعرض عدّاد المحاولات المحجوبة في شريط الحالة.
Read, Edit, Write, Grep and Bash calls that touch .env, .env.local, .env.production, .env* globs, ….envrc, env.example and the committed templates .env.example / .env.sample / .env.template / .env.dist alone..env file without revealing it: git rm --cached .env, git check-ignore .env, ls .env, test -f .env, echo .env >> .gitignore. Any chaining (;, &&, |, $( ), backticks) turns the exemption off.🛡️ أسرار محمية: N in the status line.! cat .env) is not blocked: it guards the model, not you./plugin marketplace add ABDUAZIZX/env-guard
/plugin install env-guard@env-guard
Or try it for one session:
git clone https://github.com/ABDUAZIZX/env-guard
claude --plugin-dir ./env-guard
claude plugin validate .
claude plugin test .
.en? or a path built from variables are not caught. A safety net, not a sandbox..env — on purpose, so an existing file with your keys can't be overwritten. Create it yourself: ! cp .env.example .env..env.example-style templates is allowed; .gitignore/git rm --cached clean-up commands pass; .env* globs and {.env,…} are now caught.Mods are an early-access API and may change between releases. Read any mod's code before installing it — this one is ~50 lines in
hooks/register.ts.
MIT License
hooks/register.ts 48 lines1import type { EngineInterface, Register } from 'claude-code'
2
3// .env, .env.local, .env.production, and globs like .env* — but not .envrc, env.example,
4// or the committed templates .env.example / .env.sample / .env.template / .env.dist.
5const SECRET =
6 /(^|[\/\\\s"'=({,:<`])\.env(?!\.(?:example|sample|template|dist|defaults?)(?![\w-]))(\.[\w-]+)?($|[\s"';|&>)*?[\]{},:<`])/
7
8// Single commands that name a .env file without revealing its contents.
9const ARG = String.raw`(?:"[^"$\x60\\]*"|'[^']*'|[^\s"'$\x60<>|;&]+)`
10const SAFE_BASH = [
11 /^git\s+rm\s+(?:-\S+\s+)*--cached\b/,
12 /^git\s+check-ignore\b/,
13 /^ls\b/,
14 /^(?:test|\[)\s+-[efs]\s/,
15 new RegExp(String.raw`^(?:echo|printf)(?:\s+${ARG})+\s*>>\s*(?:[\w./-]*\/)?\.gitignore\s*$`),
16]
17// Chaining, pipes, substitution: any of these could smuggle a read in next to a safe command.
18const CHAIN = /[;&|`\n\r]|\$\(|<\(|>\(/
19
20let blocked = 0
21
22function guard(target: string | undefined) {
23 return target !== undefined && SECRET.test(target)
24}
25
26export function guardBash(command: string) {
27 if (!guard(command)) return false
28 const c = command.trim()
29 return CHAIN.test(c) || !SAFE_BASH.some(re => re.test(c))
30}
31
32function deny($: EngineInterface, what: string) {
33 blocked += 1
34 $.ui.toast(`🛡️ env-guard: منعتُ الوصول إلى ${what}`)
35 $.ui.status(`🛡️ أسرار محمية: ${blocked}`)
36 return { deny: `env-guard: ${what} ملف أسرار محمي، لا تقرأه ولا تطبع محتواه.` }
37}
38
39export const register: Register = on => {
40 on('tool.call', { tool: 'Read' }, ($, e, next) => (guard(e.file_path) ? deny($, e.file_path) : next(e)))
41 on('tool.call', { tool: 'Edit' }, ($, e, next) => (guard(e.file_path) ? deny($, e.file_path) : next(e)))
42 on('tool.call', { tool: 'Write' }, ($, e, next) => (guard(e.file_path) ? deny($, e.file_path) : next(e)))
43 on('tool.call', { tool: 'Grep' }, ($, e, next) =>
44 guard(e.path) || guard(e.glob) ? deny($, e.path ?? e.glob ?? '.env') : next(e),
45 )
46 on('tool.call', { tool: 'Bash' }, ($, e, next) => (guardBash(e.command) ? deny($, e.command.slice(0, 60)) : next(e)))
47}
48