cd /news/ai-agents/guardrails-for-vibe-coders-pretoolus… · home › topics › ai-agents › article
[ARTICLE · art-163445] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Guardrails for vibe coders: PreToolUse rules that stop rm -rf and secret leaks

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.

by read5 min views1 publishedNov 3, 2026

Originally published at pilothouse.dev/blog/guardrails-for-vibe-coders.

Vibe 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.

There'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.

brew install sabr-software/tap/pilothouse   # or: curl -fsSL https://pilothouse.dev/install.sh | sh
pilothouse setup                            # installs the hooks (shows the diff first)
pilothouse policy init                      # writes a commented starter ~/.pilothouse/policy.yaml

If you only do one thing, do this:

pilothouse policy preset guarded-autopilot on
pilothouse policy preset no-secrets on

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.

Here's a hand-written ~/.pilothouse/policy.yaml that covers the same ground, so you can see how the rules work and adjust them:

version: 1
rules:
  - name: no-broad-rm-rf
    description: Recursive force-deletes of /, ~, $HOME, . or *
    match: { kind: shell }
    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|;|&|\||$)")'
    action: deny
    message: Recursive delete of a broad path is blocked. Delete the specific files instead.

  - name: no-force-push
    match: { kind: shell, command: "*git*push*" }
    when: 'has_word(command, "--force") || has_word(command, "-f")'
    action: deny
    message: Force-push is blocked. Open a PR instead.

  - name: ask-before-pushing-main
    match: { kind: shell, command: "*git*push*" }
    when: 'has_word(command, "main") || has_word(command, "master") || branch == "main"'
    action: ask
    message: This pushes to main.

  - name: no-secret-files
    match: { path: [".env", ".env.*", "*.pem", "*.key", "~/.ssh/**", "~/.aws/**"] }
    when: '!paths.all(p, p.endsWith(".example") || p.endsWith(".sample"))'
    action: deny
    message: Secrets are off limits. Use .env.example or ask me for the value.

  - name: no-credential-dumps
    match: { kind: shell }
    when: 'has_word(command, "printenv") || command.matches(r"\benv\s*($|\|)") || command.contains("gh auth token")'
    action: deny
    message: Don't print credentials into the session.

  - name: ask-curl-pipe-sh
    match: { kind: shell }
    when: 'command.matches(r"\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh)\b")'
    action: ask
    message: This runs a script straight from the internet.

  - name: no-prod-db
    match: { kind: shell, command: ["*psql*prod*", "*mysql*prod*"] }
    action: deny
    message: Use the staging database.

A few things worth knowing about the format:

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 winsask, then allow. Run any rule against a command without an agent involved:

$ pilothouse policy check
✓ ~/.pilothouse/policy.yaml  7 rules

$ pilothouse policy test "rm -rf ~"
Decision: DENY by no-broad-rm-rf
Message:  Recursive delete of a broad path is blocked. Delete the specific files instead.

$ pilothouse policy test "rm -rf node_modules"
Decision: none — no rule matched; the agent's own permission settings apply.

$ pilothouse policy test "git push --force-with-lease origin feat"
Decision: none — no rule matched; the agent's own permission settings apply.

$ pilothouse policy test "git push origin main"
Decision: ASK by ask-before-pushing-main

$ pilothouse policy test --tool Read --path ~/app/.env.example
Decision: none — no rule matched; the agent's own permission settings apply.

Those are real results from the file above. Then see what your new rules would have done to last week's actual tool calls:

pilothouse policy replay --since 7d

Most 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:

pilothouse policy add ask-push --action ask --command "git push" --command "git push *"
pilothouse policy add no-prod-db --action deny --command "psql *prod*" --message "Use the staging database."
pilothouse policy rules      # everything, in the order it is checked

These "managed" rules are matched one command at a time, so allowing npm test never allows npm test && curl … | sh.

A 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.

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.

Pilothouse 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.

Full reference: pilothouse.dev/docs and POLICY.md on GitHub.

── more in #ai-agents 4 stories · sorted by recency
── more on @pilothouse 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/guardrails-for-vibe-…] indexed:0 read:5min 2026-11-03 · —