SLOPSHOPPER

guard

Blocks risky shell commands (phone deletes, printing secrets, self-killing pkill) and strips secrets pasted into messages

newguardtoastprompt
v1.0.0NOASSERTIONupdated 2026-10-05soyakaai-studio/claude-herdr-mods/mods/guard
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · 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 guard: guard: blocked: never print a key or password file; use it as $(cat file) inside the comm ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Claude mods for Herdr — a starter kit

Claude Code now supports mods: small plugins that run inside every Claude chat. They can block a command before it runs, put a line above the prompt, show a status line, or pop a toast. If you run many Claude chats in Herdr, a few small mods make the whole setup safer and calmer.

This kit has three, each a few dozen lines you can read and edit.

ModWhat it doesWhy we built it
guardBlocks risky shell commands before Claude runs them, and strips secrets you paste by accidentA cleanup job once emptied a phone folder; pkill -f killed Claude's own shell twice; a sign-in code landed in a chat
status-lineBottom line in every chat: local time · running jobs · API creditStop asking "status?" — jobs write a small progress file, every chat shows it
inbox-band"📥 3 new reports · ✋ 1 may need you" above the promptMany chats report to one inbox file; you see it without reading it

Rules written in a prompt can be forgotten. A mod is code, so it is enforced every time.


Install (2 minutes)

git clone <this repo> ~/.claude/mods/claude-herdr-mods

Load them in every Claude chat by adding this to ~/.claude/settings.json (merge with what's there):

{
  "env": {
    "CLAUDE_CODE_PLUGIN_DIRS": "~/.claude/mods/claude-herdr-mods/mods/guard:~/.claude/mods/claude-herdr-mods/mods/status-line"
  }
}

Add :~/.claude/mods/claude-herdr-mods/mods/inbox-band if you use the inbox idea below. New chats pick them up; restart open ones.

Try it: ask Claude to run pkill -f something. You should see guard: blocked: ….

Check or test a mod yourself:

claude plugin validate ~/.claude/mods/claude-herdr-mods/mods/guard
claude plugin test     ~/.claude/mods/claude-herdr-mods/mods/guard

Make them yours

  • guard → mods/guard/hooks/register.ts, the RULES list. Each rule is a pattern and a reason. Add the mistakes your agents make.
  • status-line → set TZ_OFFSET_HOURS. Any job can show up by writing ~/.cache/progress/<name>.json:
  { "status": "running", "done": 120, "total": 400, "eta": "14:30" }

Optional credit: write {"left": 12.5} to ~/.cache/api-credit.json from a small cron job.

  • inbox-band → other chats append one line per report to ~/.config/herdr/inbox.md (- 2026-10-06T10:00Z · Chat name · what happened). You (or your main chat) write the line number you've read to ~/.config/herdr/inbox.read. The band counts what's new.

Want to change one live? In a Claude chat, ask Claude to load the plugin-authoring skill and edit the mod — with hot reloading on, changes apply as soon as the turn ends.


How we use it with Herdr

  • One "Help Desk" chat we talk to; it dispatches work to other chats in panes and never does long work itself.
  • Other chats report into the inbox file instead of typing into the Help Desk pane (so they never land in the middle of what you're typing). The inbox band shows the count.
  • Long jobs write progress files; the status line shows them in every chat, and a small pane in Herdr can show the same file.
  • The guard runs in every chat, so a rule learned once protects all of them.

Small, boring, and it removed a whole class of "wait, what just happened?" moments.


Built by Sonat Yalcinkaya (SoyakaAI). MIT licence — use, change, share.

Source 1 files
hooks/register.ts 30 lines
1import type { Register } from 'claude-code'
2
3// Rules that block a shell command before Claude runs it. Edit freely: each rule is a pattern and a reason.
4// Ours came from real mistakes; keep the ones that fit you and add your own.
5const RULES: { test: RegExp; why: string }[] = [
6  // Phones: never delete anything on a connected Android phone.
7  { test: /\badb\b[^|;&]*\bshell\b[^|;&]*\b(rm|unlink|delete)\b/, why: 'never delete anything on a phone' },
8  // Secrets: never print key files to the screen (using them inside $(cat file) is fine).
9  { test: /(?<!\$\()\b(cat|head|tail|less|more|xxd|base64|strings|od|grep|bat)\b[^|;&]*(\.env(\.local)?\b|api[-_]?key|secret|token|password|credentials)/i, why: 'never print a key or password file; use it as $(cat file) inside the command' },
10  // Self-harm: `pkill -f name` also matches the shell running it and kills Claude's own command.
11  { test: /\bpkill\s+-f\b/, why: 'pkill -f matches your own shell; use pgrep -x then kill <pid>' },
12]
13
14// Secrets pasted into a message by accident are removed before Claude sees them.
15const SECRET = /\b(sk-[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{30,}|ya29\.[A-Za-z0-9_.-]{20,}|ghp_[A-Za-z0-9]{30,}|glpat-[A-Za-z0-9_-]{20,}|xox[bpa]-[A-Za-z0-9-]{20,})\b|-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
16
17export const register: Register = on => {
18  on('prompt.submit', ($, e, next) => {
19    if (!SECRET.test(e.text)) return next(e)
20    SECRET.lastIndex = 0
21    $.ui.toast('A secret was removed from your message.')
22    return next({ ...e, text: e.text.replace(SECRET, '[secret removed]') })
23  })
24
25  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
26    const hit = RULES.find(r => r.test.test(e.command))
27    return hit ? { deny: `${$.plugin.name}: blocked: ${hit.why}.` } : next(e)
28  })
29}
30