{"slug": "guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks", "title": "Guardrails for vibe coders: PreToolUse rules that stop rm -rf and secret leaks", "summary": "A developer built Pilothouse, a rules engine that hooks into coding agents' PreToolUse points to allow, ask, or deny tool calls across Claude Code, Codex, OpenCode, and Grok Build. The tool ships presets like guarded-autopilot, positioned as a safer alternative to skipping permissions, and no-secrets, which blocks access to .env files, private keys, and credential dumps such as printenv and gh auth token while permitting .env.example. A hand-written policy.yaml example shows rules denying broad rm -rf deletes, force-pushes, and production database access, and asking before pushes to main or piping downloads into a shell.", "body_md": "*Originally published at [pilothouse.dev/blog/guardrails-for-vibe-coders](https://pilothouse.dev/blog/guardrails-for-vibe-coders).*\n\nVibe coding works until the agent runs `rm -rf` on the wrong folder, prints your `.env` into the transcript, or force-pushes over a teammate's branch. Most of us respond in one of two ways: approve every prompt by hand, or turn on \"skip permissions\" and hope.\n\nThere's a middle option. Every major coding agent now has a **PreToolUse hook**: a point right before a tool call runs where something can say allow, ask or deny. Pilothouse (which I build) puts a rules engine at that hook, for Claude Code, Codex, OpenCode and Grok Build at once. This post is the rule file I'd hand a friend, with each rule tested.\n\n```\nbrew install sabr-software/tap/pilothouse   # or: curl -fsSL https://pilothouse.dev/install.sh | sh\npilothouse setup                            # installs the hooks (shows the diff first)\npilothouse policy init                      # writes a commented starter ~/.pilothouse/policy.yaml\n```\n\nIf you only do one thing, do this:\n\n```\npilothouse policy preset guarded-autopilot on\npilothouse policy preset no-secrets on\n```\n\n`guarded-autopilot` is meant as a safer alternative to `--dangerously-skip-permissions`. It lets routine repo work run without prompts (reading, editing inside the repo, tests, builds, linters, read-only git). It blocks destructive commands like `rm -rf ~`, `git reset --hard`, force-pushes and `DROP TABLE`. And it asks before pushes, deploys, secrets and sudo. `no-secrets` blocks `.env` files, private keys, cloud credentials and credential dumps such as `printenv` and `gh auth token`, while leaving `.env.example` alone.\n\nHere's a hand-written `~/.pilothouse/policy.yaml` that covers the same ground, so you can see how the rules work and adjust them:\n\n``` js\nversion: 1\nrules:\n  # 1. Never let an agent wipe a broad path.\n  - name: no-broad-rm-rf\n    description: Recursive force-deletes of /, ~, $HOME, . or *\n    match: { kind: shell }\n    when: 'command.matches(r\"\\brm\\s+(-[a-zA-Z]*[rR][a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*[rR][a-zA-Z]*|-r\\s+-f|-f\\s+-r)\\s+(/|~/?|\\$HOME/?|\\./?|\\*)(\\s|;|&|\\||$)\")'\n    action: deny\n    message: Recursive delete of a broad path is blocked. Delete the specific files instead.\n\n  # 2. No force-push, in any spelling (but --force-with-lease is fine).\n  - name: no-force-push\n    match: { kind: shell, command: \"*git*push*\" }\n    when: 'has_word(command, \"--force\") || has_word(command, \"-f\")'\n    action: deny\n    message: Force-push is blocked. Open a PR instead.\n\n  # 3. Pushing to main needs a person.\n  - name: ask-before-pushing-main\n    match: { kind: shell, command: \"*git*push*\" }\n    when: 'has_word(command, \"main\") || has_word(command, \"master\") || branch == \"main\"'\n    action: ask\n    message: This pushes to main.\n\n  # 4. Keep agents out of secrets (templates are fine).\n  - name: no-secret-files\n    match: { path: [\".env\", \".env.*\", \"*.pem\", \"*.key\", \"~/.ssh/**\", \"~/.aws/**\"] }\n    when: '!paths.all(p, p.endsWith(\".example\") || p.endsWith(\".sample\"))'\n    action: deny\n    message: Secrets are off limits. Use .env.example or ask me for the value.\n\n  # 5. No dumping the environment or tokens into the transcript.\n  - name: no-credential-dumps\n    match: { kind: shell }\n    when: 'has_word(command, \"printenv\") || command.matches(r\"\\benv\\s*($|\\|)\") || command.contains(\"gh auth token\")'\n    action: deny\n    message: Don't print credentials into the session.\n\n  # 6. Piping a download into a shell needs a person.\n  - name: ask-curl-pipe-sh\n    match: { kind: shell }\n    when: 'command.matches(r\"\\b(curl|wget)\\b[^|]*\\|\\s*(sudo\\s+)?(sh|bash|zsh)\\b\")'\n    action: ask\n    message: This runs a script straight from the internet.\n\n  # 7. Production databases are off limits.\n  - name: no-prod-db\n    match: { kind: shell, command: [\"*psql*prod*\", \"*mysql*prod*\"] }\n    action: deny\n    message: Use the staging database.\n```\n\nA few things worth knowing about the format:\n\n`match:` fields are globs, and they're AND-ed together: `kind`, `command`, `path`, `tool`, `cwd`, `repo`, `branch`, `agent`.` when:` is a `paths`, `hour`, `weekday` and more available. `has_word()` matches whole words, so `--force-with-lease` doesn't trip `--force`.`.env` matches at any depth. Start it with `/` or `~/` to anchor it.`deny` wins`ask`, then `allow`.\nRun any rule against a command without an agent involved:\n\n``` bash\n$ pilothouse policy check\nâœ“ ~/.pilothouse/policy.yaml  7 rules\n\n$ pilothouse policy test \"rm -rf ~\"\nDecision: DENY by no-broad-rm-rf\nMessage:  Recursive delete of a broad path is blocked. Delete the specific files instead.\n\n$ pilothouse policy test \"rm -rf node_modules\"\nDecision: none â€” no rule matched; the agent's own permission settings apply.\n\n$ pilothouse policy test \"git push --force-with-lease origin feat\"\nDecision: none â€” no rule matched; the agent's own permission settings apply.\n\n$ pilothouse policy test \"git push origin main\"\nDecision: ASK by ask-before-pushing-main\n\n$ pilothouse policy test --tool Read --path ~/app/.env.example\nDecision: none â€” no rule matched; the agent's own permission settings apply.\n```\n\nThose are real results from the file above. Then see what your new rules would have done to last week's actual tool calls:\n\n```\npilothouse policy replay --since 7d\n```\n\nMost of my rules started as one tap on an approval card. When a request reaches your phone, the card offers rules built from the call: *Always allow `npm test` in api-server*, *Always ask before `git push`*, *Never allow force-pushes*. From the terminal:\n\n```\npilothouse policy add ask-push --action ask --command \"git push\" --command \"git push *\"\npilothouse policy add no-prod-db --action deny --command \"psql *prod*\" --message \"Use the staging database.\"\npilothouse policy rules      # everything, in the order it is checked\n```\n\nThese \"managed\" rules are matched one command at a time, so allowing `npm test` never allows `npm test && curl â€¦ | sh`.\n\nA repo can ship its own `.pilothouse/policy.yaml`. Its deny and ask rules apply to anyone who works in that repo. Its allow rules only count once you run `pilothouse policy trust`, because anyone can publish a repo with a permissive policy in it.\n\n`deny` rules are enforced on Claude Code, Codex, OpenCode and Grok Build alike, which is why the rule file above leans on deny for the things you never want. Keep `ask` for things a person should see.\n\nPilothouse also has a built-in rule you can't turn off: an agent can't run `pilothouse approve`, edit your policy, or change its own hook settings without a person confirming. Agents can't approve themselves.\n\nFull reference: [pilothouse.dev/docs](https://pilothouse.dev/docs) and [POLICY.md on GitHub](https://github.com/sabr-software/pilothouse/blob/main/docs/POLICY.md).", "url": "https://wpnews.pro/news/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks", "canonical_source": "https://dev.to/pilothousedev/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks-191o", "published_at": "2026-11-03 21:43:23+00:00", "updated_at": "2026-11-03 21:53:31.021514+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["Pilothouse", "Claude Code", "Codex", "OpenCode", "Grok Build"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks", "markdown": "https://wpnews.pro/news/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks.md", "text": "https://wpnews.pro/news/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks.txt", "jsonld": "https://wpnews.pro/news/guardrails-for-vibe-coders-pretooluse-rules-that-stop-rm-rf-and-secret-leaks.jsonld"}}