🚧 Agents with Guardrails

Agents with Guardrails Cheat Sheet / survival guide

Agents with Guardrails: Letting an AI Touch Your Terminal Safely

An AI agent does not just answer. It plans, calls tools, reads the results, and keeps going: running shell commands, editing
files, calling APIs, browsing. That is what makes agents useful, and what makes them risky. Guardrails are the limits you put
around an agent so a mistake, a misunderstanding or an injected instruction cannot turn into an outage or a data leak.
The rule: give the agent a small, fenced yard to work in, a leash, and a camera, and keep the keys to everything else.

What an agent is (in one loop)

1. Goal       You ask for something ("find why nginx is failing")
2. Plan       The model decides on a next step
3. Act        It calls a tool: run a command, read a file, fetch a URL, edit code
4. Observe    It reads the tool's output (which may contain untrusted text)
5. Repeat     It decides again, until it thinks it is done, or is stopped
Every pass through the loop is a chance to be wrong, to be fooled, or to spend money. Guardrails go around every step.

What can go wrong

Too much power         A broad token or root access turns a small mistake into a big one
Destructive commands   rm -rf, DROP TABLE, force push, terraform destroy, chmod -R
Misunderstood goal     "Clean up the logs" becomes "delete the logs"
Hallucinated details   A flag, path, hostname or table name that does not exist (or exists and is wrong)
Prompt injection       Text in a web page, ticket, file or log takes over the agent (see the Prompt Injection sheet)
Runaway loops          It retries forever, burns tokens or spawns processes
Cost blowups           Big contexts, many steps and paid APIs add up quickly
Data leaks             Secrets or private data end up in a prompt, a log, a URL or a message
Silent damage          Changes that look fine but break something you do not notice for days
Overconfidence         It says "done" when it is not, or hides an error

Autonomy levels: choose deliberately

0  Suggest only         The agent writes text and commands. YOU run everything            Safest, slowest
1  Read-only agent      It can inspect (read files, run status commands) but never change anything
2  Ask before acting    It can change things, but every change needs your approval
3  Approved actions     Pre-approved safe commands run alone. Everything else asks
4  Autonomous           It acts without asking, inside a sandbox with strict limits and a full audit trail
Start at level 1 or 2. Move up only after the agent has proven reliable on that task, and only inside the limits below.
Never give production, root or admin access at level 4.

The guardrail layers (use several)

1. Scope the tools         Only the tools the task needs. Prefer narrow tools ("restart service X") over broad ones ("run any shell command")
2. Scope the access        A dedicated low-privilege account. Only the folders, hosts and APIs it needs
3. Scope the credentials   Short-lived, narrow tokens. Never your personal or admin credentials. Read-only wherever possible
4. Sandbox                 A container, VM or throwaway machine with no route to production
5. Approval gates          A human confirms anything that deletes, sends, spends, deploys or changes access. Enforced in code, not just in the prompt
6. Limits and budgets      Maximum steps, time, tokens, spend and processes per run
7. Network control         Block outbound traffic, or allow only the hosts it needs
8. Logging                 Record every prompt, tool call, command and result
9. Rollback                Git, snapshots and backups, so any change can be undone
10. Kill switch            A fast way to stop it and revoke its access
11. Testing                Try it on fake data and hostile inputs before it touches anything real
12. Review                 Read diffs and logs. Someone stays accountable

Claude Code: permission modes and controls

Shift+Tab cycles the permission mode. Choose the strictest one that still lets you work:
Plan            Explores and proposes a plan. It does not edit files. Best for a first look at anything unfamiliar
Manual          Asks before every edit and command. Best while learning a new task
Accept edits    Auto-accepts file edits, still asks for other commands
Auto            A classifier reviews actions and blocks risky ones (default on recent versions)
Bypass          Skips prompts. ONLY inside a disposable container or VM with no secrets and no production access
claude --permission-mode plan             # start in plan mode
claude --max-turns 20                     # cap the number of agent steps (print mode)
claude -p "summarize the failing tests" --max-budget-usd 2    # cap spending (print mode)
claude --worktree cleanup-branch          # work in a separate git worktree, away from your main checkout
/permissions                              # view and edit allow and deny rules from inside a session
/model, /effort                           # choose a model and how hard it thinks (a smaller model is cheaper for routine work)
Rules go in settings.json (~/.claude/settings.json for you, .claude/settings.json for a project). Deny rules win over allow rules:
{
  "permissions": {
    "allow": ["Bash(git status)", "Bash(git diff:*)", "Bash(npm test:*)"],
    "deny": ["Read(./.env)", "Read(~/.ssh/**)", "Bash(rm -rf:*)", "Bash(git push --force:*)", "Bash(curl:*)"]
  }
}
Keep deny rules for secrets (.env files, ~/.ssh, cloud credentials) and for destructive or network-fetching commands.

A guard hook that blocks dangerous commands (Claude Code hooks)

A hook is your own script that runs before or after each tool call. Unlike a request in a prompt, it ALWAYS runs.
Add to settings.json:
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/usr/local/bin/guard.sh" } ] }
    ]
  }
}
/usr/local/bin/guard.sh (chmod +x; needs jq):
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // empty')
if printf '%s' "$cmd" | grep -Eq 'rm -rf|mkfs|dd if=|git push (-f|--force)|curl .*\| *(ba)?sh|chmod -R 777|DROP (TABLE|DATABASE)'; then
  echo "Blocked by guard.sh: $cmd" >&2
  exit 2            # exit code 2 blocks the call and shows the message to the agent
fi
exit 0
Test it with harmless input before you rely on it:  echo '{"tool_input":{"command":"rm -rf /tmp/x"}}' | /usr/local/bin/guard.sh; echo $?
A blocklist is a safety net, not a wall. It misses variations, so keep the permission rules, the sandbox and the approvals too.
Other useful hooks: log every command to a file, send a notification when a session ends, run a formatter or tests after edits.
Check the current hook and settings format in the Claude Code docs, because details change between versions.

A sandbox for the agent (Docker example)

docker run --rm -it \
  --network none \
  --read-only --tmpfs /tmp \
  --cap-drop ALL --security-opt no-new-privileges \
  --pids-limit 256 --memory 1g --cpus 1 \
  --user 1000:1000 \
  -v "$PWD":/work -w /work \
  ubuntu:24.04 bash
--network none          No outbound traffic at all (nothing to exfiltrate to, but the agent cannot download anything either)
--read-only             The container's own filesystem cannot be changed. /work is the only writable place
-v "$PWD":/work:ro      Mount your project READ-ONLY when the agent only needs to look
--cap-drop ALL          Remove Linux capabilities. no-new-privileges blocks privilege gains
--pids-limit, --memory, --cpus    Stop fork bombs and runaway processes from taking over the host
Give the container a COPY or a git worktree of the project, never your home directory, ~/.ssh or cloud credentials.
When it needs the network, allow specific hosts through a proxy or firewall rules instead of turning everything on.
Other options: a disposable cloud VM, a local VM (Multipass, VirtualBox, virt-manager), Podman rootless, bubblewrap or firejail,
a separate Unix user, systemd-run with limits, and stronger isolation such as gVisor or Firecracker for untrusted work.
systemd-run --user --scope -p MemoryMax=1G -p CPUQuota=100% -p TasksMax=200 bash        # limits on a plain shell

Least-privilege access for a sysadmin agent

A dedicated user with only the commands it needs (edit with visudo -f /etc/sudoers.d/ai-agent):
ai-agent ALL=(root) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/journalctl -u *
Be careful: wildcards let arguments slip through. The safest pattern is a small, reviewed wrapper script per allowed action:
ai-agent ALL=(root) NOPASSWD: /usr/local/sbin/ai-restart-nginx, /usr/local/sbin/ai-show-disk
The wrapper takes no free-form input, does one thing, logs it, and can be audited.
Other ways to limit access:
- A separate account for each API, with read-only scopes (a cloud role that can describe but not delete)
- Short-lived credentials (cloud STS tokens, Vault leases) that expire in minutes or hours
- Different credentials for dev, staging and production, and NO production credentials for experiments
- Database users with SELECT only. A read replica for the agent to query
- SSH keys restricted with forced commands in authorized_keys: command="/usr/local/sbin/ai-diag",no-port-forwarding,no-pty ssh-ed25519 AAAA...

Build it up in stages (a safe rollout)

Stage 1  Read-only diagnostics       The agent runs status and log commands and explains what it sees. It changes nothing
Stage 2  Proposals                   It writes the fix as a script or runbook step. You review and run it
Stage 3  Approved execution          It runs specific, allowlisted actions after you approve each one
Stage 4  Automation in a fence       Repeat, low-risk tasks run alone, inside a sandbox with limits, alerts and rollback
Stay at a stage until you trust the results, and drop back a stage after any incident. Tasks touching money, data or access stay at Stage 3.

Tools: narrow beats broad

Weak     "Run any shell command"                   Strong   "restart_service(name) where name is in [nginx, php-fpm]"
Weak     "Query the database"                      Strong   "Run a read-only query as a SELECT-only user, with a 10-second timeout and a row limit"
Weak     "Call any URL"                            Strong   "Fetch from an allowlisted list of domains, with no cookies or credentials"
Weak     "Write to any file"                       Strong   "Write only inside /work/output, with a size limit"
Validate tool arguments in code (names, paths, numbers, ranges) before running anything. Never pass model text straight to a shell.
Return small, clean results, and strip secrets from tool output before the model sees it.
Make actions idempotent and reversible where you can, and add a dry-run mode that the agent must use first.

Limits and budgets (stop runaways)

Max steps per task         For example 20 tool calls, then stop and report
Max time                   timeout 300 command      or a wall-clock limit for the whole run
Max spend and tokens       A per-run and per-day budget, with an alert at 80 percent
Max processes and memory   Container and cgroup limits (see the sandbox above)
Rate limits                No more than N deletes, emails or API writes per run
Retry limits               Three attempts, then stop and ask. Do not loop on the same failing command
Loop detection             Stop if the same action repeats several times with the same result
Cloud cost guard           Budget alerts on the account the agent uses, and a quota on expensive resources

Logging and audit trail

- Record: the goal, every prompt, every tool call with arguments, the output, timestamps, and who approved what
- Keep logs where the agent cannot edit or delete them (append-only, another host, or a log service)
- Redact secrets in logs, and protect the log files as sensitive data
script -q -f "agent-$(date +%F-%H%M).log"        # simple: record a whole terminal session
sudo auditctl -w /etc -p wa -k ai-agent-etc      # Linux audit: watch writes under /etc (review with ausearch -k ai-agent-etc)
journalctl -t ai-agent --since today             # if your wrapper scripts log with: logger -t ai-agent "restarted nginx"
Alert on: denied actions, unusual commands, actions outside working hours, big spends, and repeated failures.

Rollback and recovery

git commit before every agent session, git diff after, git restore to undo:   git add -A && git commit -m "checkpoint before agent"
Snapshot first: LVM or ZFS snapshots, cloud disk snapshots, VM snapshots, database backups
Use transactions for database changes, and a staging copy for rehearsals
Keep the change list small so you can review it, and prefer several small runs to one giant one
Know how to restore BEFORE you run: test a restore from your backups regularly

Kill switch (practice it)

Stop the process:     Ctrl+C, then  pkill -f agent-name      or   docker stop container-name
Cut its access:       revoke or rotate its tokens and API keys, disable its user (sudo usermod -L ai-agent), remove its sudoers file
Cut its network:      sudo iptables -I OUTPUT -m owner --uid-owner ai-agent -j DROP        (or remove it from the network in the cloud console)
Then review the logs before you restart anything. Write these steps down and try them once, in a calm moment.

Test the guardrails

- Run the agent on fake data and a disposable environment first
- Give it impossible or ambiguous tasks and see if it asks instead of guessing
- Try to make it break the rules: ask for a destructive command, or hide a canary instruction in a file (see the Prompt Injection sheet)
- Confirm the deny rules, hooks, sandbox and network limits really block things (try the forbidden action and look for the block)
- Simulate failure: a tool that errors, a full disk, a slow API. Does it stop, retry forever or lie about it?
- Keep a small test suite of tasks with known correct outcomes. Re-run it after any change of model, prompt, tools or permissions
- Watch cost and step counts during tests

Red flags: stop the agent if it...
- Runs commands unrelated to the task, or touches paths and hosts you did not mention
- Tries to disable its own limits, edit its own config, hooks or permissions, or use sudo you did not expect
- Asks for secrets, tokens or wider access "to finish the job"
- Repeats the same failing step again and again
- Claims success without evidence, or hides an error
- Reads far more than it needs, or tries to send data somewhere new
- Behaves oddly right after reading a web page, file or message (possible injection)

Design checklist before you give an agent real access

[ ] What is the exact task, and what is out of scope?
[ ] What autonomy level is justified? Is it staged (read-only first)?
[ ] Which tools does it have, and are they as narrow as possible?
[ ] Which account and credentials does it use? Are they least privilege, short-lived and separate from production?
[ ] Does it run in a sandbox, with network limits?
[ ] Which actions need human approval, and is that enforced in code?
[ ] Are there limits on steps, time, tokens, spend and processes?
[ ] Does untrusted text reach it? Did you remove one leg of the lethal trifecta (private data, untrusted content, outbound channel)?
[ ] Is everything logged somewhere it cannot tamper with?
[ ] Can you roll back, and have you tested it?
[ ] Do you have a kill switch, and does someone own reviewing the results?
[ ] Have you tested it on fake data and hostile input?

Common failure patterns and fixes

The agent deletes too much           Use dry-run first, allowlist paths, approval for deletes, and backups
It never finishes                    Step, time and retry limits, plus loop detection
It keeps "fixing" the wrong thing    Give a narrower goal and clearer success checks. Use plan mode first
It ran up a bill                     Budgets and alerts, a smaller model for routine steps, and shorter contexts
It leaked a secret                   Keep secrets out of its context, use scoped tokens, block outbound network, and rotate anything exposed
It followed text from a web page     Isolate untrusted content, remove the outbound leg, require approval for actions
It changed its own rules             Keep config, hooks and permissions in a place it cannot write to (read-only mount, root-owned file)
It works in test, breaks in prod     Do not give it production access. Rehearse in staging with the same limits
Nobody knows what it did             Turn on logging first, and keep the audit trail somewhere it cannot alter

Multi-agent and MCP notes

- Every extra agent or tool server is more attack surface. Add them one at a time
- Install only MCP servers and plugins you trust. Read what tools they expose and what data they can reach
- One agent's output is untrusted input for the next agent. Validate it between steps
- Give each agent its own credentials and its own limits
- Do not let one agent approve another agent's risky actions. A human (or a code check) approves

Glossary

Agent            An AI that plans and uses tools to take actions toward a goal
Guardrail        A limit that keeps an agent's behavior safe (permission, sandbox, budget, approval, hook)
Human in the loop  A person approves actions before they happen
Sandbox          An isolated environment where mistakes cannot reach real systems
Least privilege  Only the access the task needs
Hook             A script that runs automatically at set points, such as before a tool call
Allowlist        A list of permitted things. Everything else is blocked
Egress           Outbound network traffic
Audit trail      A tamper-resistant record of what happened
Kill switch      A fast way to stop an agent and remove its access

Tips

    Start read-only. An agent that can only look is very hard to regret.

    Enforce limits in code and in the environment (permissions, sandbox, network rules, hooks), not only in the prompt.

    Give the agent its own low-privilege account with short-lived, narrow credentials. Never your admin or production keys.

    Approve anything that deletes, sends, spends, deploys or changes access, and read each command before you approve it.

    Commit before every session and read git diff after it, so every change can be reviewed and reverted.

    Cap steps, time, tokens and spend, and stop on repeated failures instead of letting it loop.

    Log every tool call in a place the agent cannot edit, and watch for actions that do not match the task.

    Break the lethal trifecta: keep private data, untrusted content and an outbound channel out of the same agent.

    Practice the kill switch, test the guardrails on fake data, and re-test after every change.

    Autonomy is earned. Move up a level only after reliable results, and drop back a level after any surprise.