SLOPSHOPPER

secret-guard

Blocks reads of .env files and printing of API keys, with a red warning above the prompt.

newbandguardprompt
v0.1.0no licenseupdated 2026-10-03sverma24/claude-code-mods/secret-guard
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · secret-guard
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(cat .env) ⎿ Denied by secret-guard: secret-guard blocked this: Command touching a .env file. Do not read .env files or ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM 🛡 secret-guard blocked Bash: Command touching a .env file (cat .env) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
🛡 secret-guard blocked Bash: Command touching a .env file (cat .env)
README

Three Claude Code mods, built by asking Claude

These are the mods from the video "Claude Code Mods: 3 Official Mods + 3 I Built Without Writing Code". Claude Code wrote each one from a single sentence. They were tested in Claude Code 2.1.288.

ModWhat it doesPrompt used
secret-guardBlocks reads of .env files and printing API keys, and shows a red warning above the promptMake me a mod called secret-guard. If Claude tries to read a .env file or print an API key, block it and show a red warning above the prompt saying what it blocked.
checkpointCommits the files Claude edited after every turn, and adds /rollback to undo the last checkpointMake me a mod called checkpoint. After every turn where Claude edited files, commit the changes to git as a checkpoint and show a toast. Add a /rollback command that undoes the last checkpoint.
touched/touched opens a pane listing every file Claude read or edited this sessionMake me a mod with a /touched command. It opens a pane that lists every file Claude has read or edited in this session, with a count for each. (then: show paths relative to the project folder and put edited files in orange)

Check before you install

A mod runs with your permissions and isn't sandboxed. Read what each one does first:

claude plugin validate ./secret-guard

The calls: line lists everything the mod can do. None of these three make network requests or read environment variables.

Install

Requires Claude Code 2.1.287 or later (claude update). Inside a Claude Code session:

/plugin install secret-guard --marketplace sverma24/claude-code-mods

Or from your shell:

claude plugin marketplace add sverma24/claude-code-mods
claude plugin install secret-guard@sauravgenai

Swap secret-guard for checkpoint or touched. Run /reload-plugins in an open session, or start a new one.

Or try one for a single session

Clone this repo, then:

claude --plugin-dir ./secret-guard

That loads it for one session only. Run /plugin and you'll see 1 mod active.

Limits

  • secret-guard matches patterns in the tool call. A command that builds the path or variable name indirectly can get past it. Treat it as a seatbelt, not a vault.
  • checkpoint only tracks files changed with the Edit and Write tools, not files changed through shell commands. /rollback refuses if you have uncommitted changes.
Source 2 files
hooks/register.tsx 83 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Block } from '../types'
5
6const last = atom({ plugin: 'secret-guard', key: 'last' } as const, null)
7
8const ENV_FILE = /(^|[\/\s'"=])\.env(\.[\w.-]+)?($|[\s'"])/
9const ENV_SAMPLE = /\.env\.(example|sample|template)\b/
10const KEY_VAR = /\$\{?[A-Za-z0-9_]*(API_?KEY|SECRET|TOKEN|PASSWORD)[A-Za-z0-9_]*\}?/i
11const KEY_LITERAL =
12  /\b(sk-[A-Za-z0-9_-]{16,}|sk-ant-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/
13const DUMP_ENV = /(^|[;&|]\s*)(printenv|env|export\s+-p|set)\s*($|[;&|])/
14const PRINTS = /\b(echo|printf|cat|print|printenv)\b/
15
16const isEnvPath = (p: unknown): p is string =>
17  typeof p === 'string' && ENV_FILE.test(p) && !ENV_SAMPLE.test(p)
18
19const inspect = (e: Record<string, any>): { reason: string; target: string } | null => {
20  for (const key of ['file_path', 'path', 'pattern', 'notebook_path']) {
21    if (isEnvPath(e[key])) return { reason: 'Read of a .env file', target: e[key] }
22  }
23
24  const cmd = e.command
25  if (typeof cmd === 'string') {
26    if (ENV_FILE.test(cmd) && !ENV_SAMPLE.test(cmd)) {
27      return { reason: 'Command touching a .env file', target: cmd.slice(0, 60) }
28    }
29    if (KEY_LITERAL.test(cmd) && PRINTS.test(cmd)) {
30      return { reason: 'Printing an API key', target: cmd.slice(0, 60) }
31    }
32    if (PRINTS.test(cmd) && KEY_VAR.test(cmd)) {
33      return { reason: 'Printing an API key variable', target: cmd.slice(0, 60) }
34    }
35    if (DUMP_ENV.test(cmd)) {
36      return { reason: 'Dumping environment variables', target: cmd.slice(0, 60) }
37    }
38  }
39
40  return null
41}
42
43export const register: Register = on => {
44  on('tool.call', async ($, e, next) => {
45    const hit = inspect(e as Record<string, any>)
46
47    if (hit === null) {
48      return next(e)
49    }
50
51    const block: Block = { tool: String(e.tool), ...hit }
52    await update($, last, () => block)
53
54    return {
55      deny: `secret-guard blocked this: ${hit.reason}. Do not read .env files or print secrets.`,
56    }
57  })
58
59  on('prompt.submit', async ($, e, next) => {
60    await update($, last, () => null)
61
62    return next(e)
63  })
64
65  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
66    const block = await read($, last)
67
68    if (block === null) {
69      return next(e)
70    }
71
72    const { Box, Text } = $.ui.resolve(e)
73
74    return (
75      <Box>
76        <Text color="red" bold>
77          🛡 secret-guard blocked {block.tool}: {block.reason} ({block.target})
78        </Text>
79      </Box>
80    )
81  })
82}
83
types/index.d.ts 8 lines
1export type Block = { tool: string; reason: string; target: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'secret-guard': { last: Block | null }
6  }
7}
8