SLOPSHOPPER

guardrails

Blocks Cloudflare write commands, attribution lines in commits and PRs, and claude/ branch names

newguard
★ 5v0.3.1MITupdated 2026-10-03arasovic/claude-code-mods/guardrails
A shopper browsing a rack in a slop shop
README

guardrails

Blocks three kinds of shell commands: Cloudflare writes, attribution lines in commits and PRs, and claude/ branch names. Claude gets the reason back and can retry the right way.

What it blocks

CommandWhy
cf deploy, cf migrate, cf initCloudflare writes stay on each project's Wrangler flow.
git commit, gh pr, gh issue with a Co-Authored-By or "Generated with Claude" lineNo tool attribution in commits, pull requests or issues.
git checkout -b, git switch -c, git branch, git push naming a claude/ branchBranches use a purpose prefix such as fix/ or chore/.

Reading credential files is secret-guard's job.

Limits

  • It reads the command text only. git commit -F file, shell aliases and scripts get through. Use permission deny rules in settings.json where you need a hard guarantee.
  • The rules are fixed in the code. Edit hooks/register.ts to change them.

Install

claude plugin marketplace add arasovic/claude-code-mods
claude plugin install guardrails@claude-code-mods

Restart Claude Code.

Develop

claude plugin validate .
claude plugin test .
../typecheck.sh guardrails
Source 1 files
hooks/register.ts 19 lines
1import type { Register } from 'claude-code'
2
3// Commands refused on their text. Credential reads are secret-guard's.
4const FORBIDDEN = [
5  { re: /\bcf\s+(deploy|migrate|init)\b/, why: 'cf write commands are never run by Claude; deploys stay on the Wrangler flow.' },
6  { re: /\b(git\s+commit|gh\s+(pr|issue))\b[\s\S]*(Co-Authored-By|Generated with \[?Claude)/i, why: 'Remove the Co-Authored-By / tool-attribution line and retry.' },
7  { re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch|push)\b[^|;&]*(?:[\s:+]|refs\/heads\/)claude\//, why: 'Branches never use the claude/ prefix; use fix/, style/, chore/ etc.' },
8]
9// ponytail: matches command text only; `git commit -F file`, aliases and scripts slip through. Permission deny rules for hard guarantees.
10
11export const forbiddenReason = (command: string) => FORBIDDEN.find(f => f.re.test(command))?.why
12
13export const register: Register = on => {
14  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
15    const reason = forbiddenReason(e.command)
16    return reason ? { deny: `${$.plugin.name}: ${reason}` } : next(e)
17  })
18}
19