KaairosGet the kit
Guide · 8 min read

AI coding agent guardrails: a checklist

The threat model, the layers that contain it — permissions, hooks, secret guards, network approval, sandboxing — and a copy-run checklist for what to automate versus stop and ask.

A guardrail system for an AI coding agent is five layers, not one setting: permissions (what the agent may touch at all), hooks (enforcement before and after each tool call), secret guards (keeping credentials unreadable), network approval (no silent outbound calls), and a sandbox for anything you cannot fully trust. Claude Code gives you the permissions block and the hooks API to build all of it; this guide wires them into a defense-in-depth setup and ends with a checklist you can run against your own settings.json.

Start from what already ships. Claude Code's free /security-review command and security-guidance plugin are a real baseline for catching risky code patterns, and you should keep them running. They are review tools, though — they do not stop the agent from doing damage in the first place. The layers below are the enforcement half.

The threat model

An agent that can edit files, run shells, and fetch URLs has three failure modes worth designing against: destructive commands (an rm -rf or a force push that eats work), exfiltration (secrets or source pushed to an attacker-controlled endpoint), and privilege escalation (the agent acquiring or writing code that acquires more access than the task needs). These are not hypothetical at scale.

Velocity multiplies the risk surfaceApiiro found that AI-assisted developers ship three to four times more code but introduce roughly ten times more security issues — with privilege-escalation paths up 322% and architectural design flaws up 153% (Apiiro, Sep 2025). More code through the same agent means more chances for each failure mode to land, which is exactly why the guardrails have to be structural rather than advisory.
Failure modeConcrete examplePrimary control
Destructive commandrm -rf on an absolute path, non-lease force pushPreToolUse deny-list on Bash
Exfiltrationcurl POST of a file to an external hostDeny curl/wget, gate WebFetch
Secret exposureAgent reads .env or a private key into contextPermission deny on secret paths
Privilege escalationchmod 777, sudo, editing CI credentialsDeny + ask rules, sandbox
Indirect injectionInstruction hidden in a fetched page or READMEPostToolUse output scan

Layer 1 — Permissions (allow / ask / deny)

Permissions decide what the agent can reach before any hook runs. The model is allow for the safe and repetitive, ask for the consequential, deny for the never. Anchor the setup here, because a path the agent cannot read cannot be leaked and a tool it cannot call cannot be abused:

{
  "permissions": {
    "allow": [
      "Bash(npm run test:*)",
      "Bash(npm run lint:*)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "ask": [
      "WebFetch",
      "Bash(git push:*)",
      "Bash(npm publish:*)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./**/*.pem)",
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Bash(sudo:*)"
    ]
  }
}

The deny list is the load-bearing part. Everything else is convenience; this is the boundary an injected or over-eager instruction physically cannot cross.

Layer 2 — Hooks (PreToolUse and PostToolUse)

Permissions are coarse — allow or not. Hooks add judgement: a PreToolUse hook inspects the exact command and can veto it, and a PostToolUse hook inspects the result and can flag it. Community references like dwarvesf/claude-guardrails and disler/claude-code-hooks-mastery are good reading for the range of what is possible. The minimal wiring:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.py" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "WebFetch|Bash|Read",
        "hooks": [
          { "type": "command", "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/guard-output.py" }
        ]
      }
    ]
  }
}

The PreToolUse guard exits 2 to block a catastrophic command and 0 to allow; the PostToolUse guard warns without rolling anything back. For the exact scripts and the reasoning behind block-early / warn-late, see harden Claude Code against prompt injection, and the hooks cookbook for the other lifecycle events.

Layer 3 — Secret guards

Denying .env and key files is the floor, not the ceiling. Two more moves matter: exclude secret paths from what the agent can even see when it lists a directory, and add a PreToolUse check that refuses commands which would print environment variables (env, printenv, cat .env). The goal is that a credential never enters the context window in the first place — once it is in context, it can end up in a diff, a log, or a fetched request.

Layer 4 — Network and curl approval

Every guardrail set treats outbound network access as consequential. Deny raw curl and wget so nothing can silently POST your data outward, and route legitimate fetches through WebFetch under an ask rule so a human sees the destination. This is the single most effective exfiltration control, because it removes the tool rather than trying to detect misuse of it.

Layer 5 — Sandbox guidance

Isolate what you cannot vetFor unfamiliar repositories, untrusted MCP servers, or any run where the agent will execute code it just wrote, work inside a disposable environment — a container or a throwaway VM with no host credentials mounted and no production network path. A sandbox turns 'the agent did something destructive' into 'the container is gone', which is the outcome you want. See the Claude Code security docs for the current sandboxing options.

What to automate vs stop and ask

Guardrails only survive if they match how work actually flows. Automate the reversible and repetitive; stop and ask for the consequential and irreversible.

  • Automate (allow): running tests, linters, formatters, type-checks, git status/git diff, reading source files, building.
  • Stop and ask: git push, publishing packages, any outbound fetch, editing CI/CD or infrastructure config, installing new dependencies, touching migrations.
  • Never (deny): reading secret files, raw curl/wget, sudo, recursive deletes of absolute paths, world-writable permissions.

The guardrails checklist

Run this against your project's settings.json and .claude/hooks/. Each item is either present or it is a gap:

  1. permissions.deny blocks .env, .env.*, secrets/**, and private-key globs.
  2. permissions.deny blocks raw curl, wget, and sudo.
  3. permissions.ask gates WebFetch, git push, and package publishing.
  4. A PreToolUse hook on Bash blocks recursive absolute deletes, fork bombs, pipe-to-shell, non-lease force pushes, and chmod 777.
  5. The PreToolUse deny-list is short enough that it never fires on ordinary commands (test it).
  6. A PostToolUse hook scans WebFetch/Bash/Read output for injection-shaped text and warns without blocking.
  7. A PreToolUse check refuses commands that would print environment variables.
  8. Untrusted repos and self-executed code run in a sandbox with no host credentials or production network.
  9. New-dependency installs require approval rather than running automatically.
  10. Claude Code's free /security-review runs on generated code before it is committed.
  11. Every guardrail has been observed firing at least once, and re-tested after the last settings.json change.
What a maintained kit addsYou can assemble all of the above from open-source hooks and the free Claude Code tooling — it is public, and this guide shows the shape of it. What takes ongoing effort is keeping the deny-lists, hook API, and threat-model templates current as Claude Code changes, and confirming every borrowed component is under a permissive licence. The Kaairos Complete bundle is that maintained, license-audited layer wrapped around the free /security-review and security-guidance plugin, alongside the Engineer Kit.

The mistake to avoid

The failure is treating guardrails as a one-time config commit. The threat surface grows every time you add an MCP server, a dependency, or a new tool, and a hook silently breaks the moment a settings.json typo slips in. Re-run the checklist when the setup changes, and pair enforcement with review — read how to review AI-generated code for security for the half that catches what the hooks let through.

Sources

Keep reading