{"slug": "show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded", "title": "Show HN: Guardrails for Claude Code: blocks rm -RF, reports what loaded", "summary": "Developer ahmed-alstaty released a Claude Code plugin called Guardrails that blocks destructive shell commands such as recursive removal of /, ~, $HOME, ., .., *, the project directory, or anything outside it, and reports at session start which instruction files, rules, skills, hooks and plugins Claude actually loaded. The plugin enforces its rules through dependency-free python3 (3.8 or newer) scripts using PreToolUse and SessionStart hooks, tokenizes every Bash command to judge each simple command separately, and also denies edits to secrets paths including .env, *.pem, *.key, id_rsa*, credentials.json and .aws/credentials. It installs via the repository's own marketplace, alstaty, with the commands /plugin marketplace add ahmed-alstaty/guardrails-plugin and /plugin install guardrails@alstaty.", "body_md": "A Claude Code plugin that puts hard limits around what Claude can do in your project, tells you what Claude actually loads at session start, and lints your `CLAUDE.md` files against Anthropic's own guidance.\n\nThree features, all enforced with plain `python3` scripts and no dependencies:\n\n1. **Enforcement hooks** (`PreToolUse` ): destructive shell commands and edits to secrets or protected paths are denied before the tool runs. Deployment and infrastructure files require your approval.\n2. **Session start report** (`SessionStart` ): a compact`OK / WARN / SKIPPED` checklist of instruction files, rules, skills, hooks and plugins, with warnings for the mistakes the docs warn about.\n3. **Skills** :`/guardrails:guardrails-lint` ,`/guardrails:guardrails-status` and`/guardrails:guardrails-test` .\n\nThe repository is its own marketplace (`alstaty`), so it takes two commands in a Claude Code session:\n\n```\n/plugin marketplace add ahmed-alstaty/guardrails-plugin\n/plugin install guardrails@alstaty\n```\n\nOr from your shell:\n\n```\nclaude plugin marketplace add ahmed-alstaty/guardrails-plugin\nclaude plugin install guardrails@alstaty            # add --scope project to enable it for the whole repo\n```\n\nFrom a local checkout, replace the GitHub shorthand with the path: `claude plugin marketplace add ./guardrails-plugin`. To try it for one session without installing: `claude --plugin-dir ./guardrails-plugin`.\n\nRequirements: Claude Code with plugin support and `python3` (3.8 or newer) on `PATH`. No pip packages.\n\nEvery `Bash` command is tokenized (quotes, escapes, `&&`, `||`, `;`, `|`, `$( )`, backticks, `bash -c`, `eval`, `sudo`/` env`/` xargs` wrappers) and each simple command is judged on its own, so `true && rm -rf /` is caught and `rm build/old.log` is not.\n\n| Category | Denied | Asks for approval | Allowed | \n|---|---|---|---|\n| `rm` /`shred` /`unlink` | recursive removal of `/` ,`~` ,`$HOME` ,`.` ,`..` ,`*` , the project directory, anything outside it, an unexpanded`$VAR/` ,`--no-preserve-root` , protected or secrets paths | non-recursive `rm` of a file outside the project;`xargs rm -r` ;`find` with`-delete` and no filter | files and directories inside the project ( `rm -rf build` ,`rm -rf node_modules` ) | \n| `git` | `push --force` /`-f` /`+ref` /`--mirror` to`main` or`master` (current branch is detected when no refspec is given); pushing a deletion of`main` /`master` ;`reset --hard` ;`clean -fd` /`-fx` ;`branch -D` ;`checkout -- .` ,`checkout .` ,`restore .` ;`filter-branch` ,`filter-repo` | `push --force` with an unknown branch;`--force-with-lease` to main;`checkout -- <file>` ,`restore <file>` ;`clean -f` ;`stash drop/clear` ;`reflog expire` ;`gc --prune` | everything else, including `push -f` to a feature branch | \n| Databases | `DROP DATABASE` ,`DROP SCHEMA` ,`dropdb` ,`dropDatabase()` ,`FLUSHALL` /`FLUSHDB` ,`db:drop` ,`migrate reset` (inline,`-c` /`-e` , or heredoc, when a database client or migration tool is in the command) | `DROP TABLE` ,`TRUNCATE` ,`DELETE FROM` without`WHERE` |  | \n| Permissions | `chmod -R 777` ,`chmod/chown -R` on root or outside the project |  |  | \n| Remote code | `curl ... \\| sh` ,`wget ... \\| sudo bash` ,`bash <(curl ...)` ,`sh -c \"$(curl ...)\"` |  | `curl ... \\| jq` | \n| Disks and system | `mkfs*` ,`wipefs` ,`fdisk` ,`dd of=/dev/...` ,`> /dev/sd*` , fork bombs,`shutdown` /`reboot` ,`kill -1` ,`crontab -r` ,`mv x /dev/null` | `sudo su` ,`sudo -i` |  | \n| Shell writes | redirection ( `>` ,`>>` ,`&>` ,`tee` ,`cp` /`mv` destination,`sed -i` ,`truncate` ) into a secrets file, a protected path, or`.git/` | writes outside the project or into an ask path |  | \n\nFile tools (`Edit`, `Write`, `MultiEdit`, `NotebookEdit`) are judged by path:\n\n| Verdict | Paths | \n|---|---|\n| Denied | secrets: `.env` ,`.env.*` ,`*.pem` ,`*.key` ,`*.p12` ,`*.pfx` ,`*.jks` ,`id_rsa*` ,`id_ed25519*` ,`credentials.json` ,`service-account*.json` ,`secrets/**` ,`.netrc` ,`.htpasswd` ,`.aws/credentials` ,`.npmrc` ,`.pypirc` ; protected:`.git/**` ; anything outside the project directory (the session scratchpad is exempt) | \n| Asks | `Dockerfile*` ,`docker-compose*.yml` ,`compose*.yml` ,`.github/workflows/**` ,`.gitlab-ci.yml` ,`terraform/**` ,`*.tf` ,`k8s/**` ,`kubernetes/**` ,`helm/**` ;`migrations/**` only when an edit deletes content or a`Write` overwrites an existing migration | \n| Allowed | `.env.example` ,`.env.sample` ,`.env.template` ,`*.example` ,`*.sample` ,`*.template` ,`*.pub` , and everything else inside the project | \n\nReading is never blocked; `cat .env` goes through so Claude can still understand configuration.\n\nA block is never silent. The hook returns the documented `permissionDecision: \"deny\"` with a `permissionDecisionReason` that Claude sees, plus a `systemMessage` for you, and echoes the reason to stderr. Every reason ends with how to proceed:\n\n```\nGuardrails blocked: git push --force to main rewrites shared history. To allow once, run the command yourself in your terminal. To allow always, add it to .claude/guardrails.json allow_commands.\n```\n\nIf the hook itself fails (unreadable input, internal error) it returns `ask`, not `allow`.\n\nCreate `.claude/guardrails.json` in the project (committed, applies to everyone) or `~/.claude/guardrails.json` (just you). Both extend the defaults; lists are appended, nothing is replaced.\n\n```\n{\n  \"allow_commands\": [\"rm -rf /tmp/scratch*\", \"git push --force origin main\"],\n  \"block_commands\": [\"npm publish*\", \"terraform apply*\"],\n  \"protected_paths\": [\"package-lock.json\", \"docs/adr/**\"],\n  \"ask_paths\": [\"config/production.yml\"],\n  \"secrets_globs\": [\"*.token\", \"config/keys/**\"],\n  \"allow_paths\": [\"Dockerfile\", \".env.ci\"]\n}\n```\n\n- `allow_commands` and`block_commands` are shell-style globs matched against the whole command and against each simple command inside it.`block_commands` wins over`allow_commands` ;`allow_commands` wins over the built-in rules.\n- `protected_paths` are always denied,`ask_paths` prompt you,`secrets_globs` are denied, and`allow_paths` is checked first and exempts a path from all of them.\n- A pattern without `/` matches a file name anywhere (`*.pem` ); a pattern with`/` matches the path relative to the project root (`secrets/**` ); an absolute or`~/` pattern matches outside the project.\n\nThe session report shows which config files were loaded and warns about invalid JSON or unknown keys. A broken config never disables enforcement; the defaults keep applying.\n\nAt startup, resume and `/clear` the plugin prints this checklist in your terminal (as a system message) and hands Claude a short summary, so Claude knows the guardrails are on and will report a block instead of trying to route around it. Run `/guardrails:guardrails-status` to see the full report again at any time:\n\n```\nGUARDRAILS session report  (cwd: /work/app)\n\nInstruction files (load at launch)\n  WARN    [project] CLAUDE.md (243 lines) - over 200 lines (243); @import missing: docs/setup.md\n  OK      [user] ~/.claude/CLAUDE.md (31 lines)\n  SKIPPED AGENTS.md present but NOT read: a CLAUDE.md/CLAUDE.local.md exists at cwd or above ...\n\nRules (.claude/rules)\n  OK      [project] .claude/rules/api.md (12 lines) paths: src/api/**/*.ts (on demand)\n\nNested instruction files (load when Claude reads files there)\n  OK      packages/web/CLAUDE.md (18 lines) on demand\n\nSkills\n  WARN    [project] /deploy (.claude/skills/deploy/SKILL.md): no description, Claude cannot decide when to use it\n\nHooks\n  OK      .claude/settings.json: PostToolUse(1)\n  SKIPPED .claude/settings.local.json: not present\n  OK      plugin guardrails: PreToolUse, SessionStart (this plugin)\n\nPlugins\n  OK      guardrails@alstaty v1.0.1 (enabled in user settings)\n\nGuardrails\n  OK      config: .claude/guardrails.json (allow_commands 2, block_commands 1, ...)\n\nSummary: 7 OK, 2 WARN, 2 SKIPPED. Run /guardrails:guardrails-lint to fix instruction files.\n```\n\nWarnings cover: instruction files over 200 lines or over 4 MiB (Claude Code skips those), `@imports` that point at missing files, the same subject under \"always\" in one file and \"never\" in another, repeated `IMPORTANT`/` ALWAYS`/` NEVER`, `CLAUDE.local.md` not in `.gitignore`, skills without a `name` or `description`, `SKILL.md` files that are a summary with no steps and no pointer to steps, `AGENTS.md` present but not read (or present without a `CLAUDE.md`), invalid settings JSON, and plugins enabled in settings but not installed.\n\nTwo channels, two costs. The full report is a system message: it is shown to you and never enters the model, so it is free. What Claude receives is a few lines (the OK / WARN / SKIPPED counts, up to four warnings, and the instruction not to route around a block), about 150 to 300 tokens once per session start, cached on every later turn. Each denied or asked tool call adds one sentence, about 40 tokens. Running `/guardrails:guardrails-status` or `/guardrails:guardrails-lint` puts that output into the conversation like any other tool result, so use them when you want them, not on every session.\n\n| Command | What it does | \n|---|---|\n| `/guardrails:guardrails-lint [files]` | Lints every instruction file that loads (or the files you name) against the documented rules: line count vs 200, procedures that should be skills, path-by-path directory descriptions Claude can derive from code, repeated emphasis words, vague rules, broken `@paths` , duplicated rules across nested files, contradictions. Writes a suggested trimmed copy of each file and a`SKILL.md` draft for each procedure into a scratch directory. It changes nothing until you pick what to apply. | \n| `/guardrails:guardrails-status` | Re-runs the session report on demand. | \n| `/guardrails:guardrails-test [\"cmd\" ...] [--path FILE]` | Dry-runs the blocklist against a built-in sample set, or the commands and paths you pass, and prints `ALLOW` /`ASK` /`DENY` with the reason. Nothing is executed. | \n\nAll three are user-invoked only (`disable-model-invocation: true`), so they cost no context until you call them.\n\n```\ntests/run_tests.sh\n```\n\nThe script copies a fixture project to a temporary directory, feeds sample hook JSON to the real hook scripts and checks the decisions: destructive `rm` blocked, safe `rm` allowed, `.env` edits blocked, force push to `main` blocked, normal `git push` allowed, `Dockerfile` edits ask, config overrides, fail-safe on garbage input, the session report's warnings, the linter's findings, and the manifests. It exits 0 when everything passes.\n\nTo check the manifests with the CLI: `claude plugin validate .` (marketplace) and `claude plugin validate .claude-plugin/plugin.json --strict`.\n\n- **This is enforcement for Claude Code only.** The hooks run when Claude Code calls a tool. They do nothing for commands you type yourself, for other agents, or for scripts Claude writes and you run later.\n- **It is not a sandbox.** A hook sees a command string, not what the command does. A build script that deletes files, a`make clean` target,`docker compose down -v` , or a program invoked through a name the parser does not recognise all pass. Use Claude Code's sandbox and permission modes for isolation; use this plugin for guardrails on the common mistakes.\n- **It does not replace backups or branch protection.** Force pushes to`main` are blocked here, but the remote should refuse them too. Commit often.\n- **Heuristics have edges.** The tokenizer handles the common shell constructs, not every one. Variables are not expanded (a command built from`$VAR` is judged on its text, and`rm -rf $VAR/` is denied for that reason). Branch detection for a bare`git push -f` runs`git symbolic-ref` in the project; if that fails the hook asks instead of guessing. The lint and contradiction checks are pattern-based and will produce some false positives; the skill tells Claude to verify each one before reporting it.\n- **`python3` must be on `PATH`** for the hook processes. If it is missing, Claude Code reports a hook error and the tool call proceeds; the session report will not appear, which is your signal.\n- **Windows** : paths are handled with forward slashes and the scripts avoid POSIX-only calls, but the shell rules target bash-style commands. PowerShell commands are not parsed.\n\n```\nguardrails-plugin/\n├── .claude-plugin/\n│   ├── plugin.json           plugin manifest\n│   └── marketplace.json      marketplace \"alstaty\" listing this plugin at \".\"\n├── hooks/hooks.json          PreToolUse (Bash; Edit|Write|MultiEdit|NotebookEdit) and SessionStart\n├── scripts/\n│   ├── guardrails_common.py  config loading, path classification, hook output\n│   ├── shell_guard.py        tokenizer and command rules\n│   ├── pretooluse.py         PreToolUse entry point\n│   ├── instruction_scan.py   instruction-file discovery and heuristics\n│   ├── session_report.py     SessionStart entry point (also --text)\n│   ├── lint_claude_md.py     linter used by guardrails-lint\n│   └── dry_run.py            used by guardrails-test\n├── skills/\n│   ├── guardrails-lint/      SKILL.md + reference.md\n│   ├── guardrails-status/SKILL.md\n│   └── guardrails-test/SKILL.md\n└── tests/run_tests.sh        hook tests with a fixture project\n```\n\nMIT. Copyright (c) 2026 Ahmad Alstaty.", "url": "https://wpnews.pro/news/show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded", "canonical_source": "https://github.com/ahmed-alstaty/guardrails-plugin", "published_at": "2026-09-29 09:51:06+00:00", "updated_at": "2026-09-29 10:18:45.330149+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["Claude Code", "Anthropic", "ahmed-alstaty", "Guardrails", "alstaty", "python3"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded", "markdown": "https://wpnews.pro/news/show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded.md", "text": "https://wpnews.pro/news/show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded.txt", "jsonld": "https://wpnews.pro/news/show-hn-guardrails-for-claude-code-blocks-rm-rf-reports-what-loaded.jsonld"}}