Mindket

AI agent guardrails: sandboxes, permission rules and hooks

A black banner titled "Harness engineering" over the line "The loop, tools and guardrails around an agent".

AI agent guardrails are the limits around what an agent can do. The ones that hold sit outside the model: a sandbox that confines files and network access, permission rules, hooks that check each tool call in code, and approvals for actions with side effects. Instructions and classifiers come second, because prompt injection can talk a model past them.

In this lesson

  • Put the hard limits in the environment: a sandbox, a filesystem boundary and an egress allowlist.
  • Keep credentials outside the sandbox, so a fooled agent has nothing to send.
  • Check each tool call in code with a hook or a tool guardrail, and block what the check cannot parse.
  • Treat every allowed domain and connector as a way in, and ask a person before actions with side effects.
All courses
On this page
  1. Why a line in the prompt is not a guardrail
  2. Before you start
  3. Three parts to defend, and the one that holds when the model is fooled
  4. Lock down a coding agent: sandbox, scoped rules, an allowlist
  5. Check every tool call with a hook
  6. Tool guardrails in the OpenAI Agents SDK
  7. Prompt injection and the lethal trifecta
  8. What never to let an agent do alone
  9. What Simon Willison and Boris Cherny posted
  10. Common mistakes
  11. Try it yourself
  12. Next course: AI security
  13. Check yourself
  14. Frequently asked questions
  15. Sources

Why a line in the prompt is not a guardrail

In February 2026, during an internal red-team exercise, an Anthropic researcher phished an employee into starting Claude Code with a malicious prompt. It came in a "can you run this for me?" email and read like routine task instructions. One step asked Claude to read ~/.aws/credentials, encode the contents and POST them to an outside server. Across 25 retries, Claude sent them 24 times.

The model had no reason to refuse. The instruction came through the user, and Anthropic's model-layer defenses anchor on what the user intends, so there was nothing for a classifier to flag. In its post on how it contains Claude, Anthropic says only the environment holds in a case like this. Egress controls block the POST whatever the intent, and filesystem boundaries keep ~/.aws out of reach.

Asking a person first does not fix it. Claude Code used to ask for permission at each turn, and Anthropic's telemetry showed users approved about 93% of those prompts. The post gives the reason: the more approvals a user sees, the less attention they pay to each.

A guardrail that holds is a limit set outside the model. It decides which files the agent can read and write, which hosts it can reach, which tool calls run without a person and which checks run in code first. Anthropic sums up its own approach in one line. "Design for containment at the environment layer first, then steer behavior at the model layer."

Before you start

This is the last lesson of the harness engineering course. It picks up where long-running agents stopped, at a restart loop with --dangerously-skip-permissions, the flag that switches every prompt off. Everything here is about what goes around that flag.

Claude Code's allow, ask and deny rules have their own lesson in Claude Code settings, and hooks in Claude Code covers shell hooks in a settings file. This lesson puts the same ideas inside your own code, through the Claude Agent SDK in Python and the OpenAI Agents SDK.

Three parts to defend, and the one that holds when the model is fooled

Anthropic defends three parts of an agent. The environment is the sandbox, virtual machine, filesystem boundary and egress control around it. The model holds system prompts, classifiers, probes and training. The third is the outside content the agent can reach through MCP servers, plugins and web search. There, narrower tool permissions limit the damage: an agent with read-only database access can be deployed far more widely than one that writes to production.

Only the environment sets a hard boundary. Because models are probabilistic, Anthropic writes, measures at the model layer shape only what the agent tends to do. Its best numbers are still above zero. An attacker who adapts also does far better than one who tries once.

Permission rules and hooks sit in between, in the agent harness. A check written in code holds whatever the model believes. A person clicking yes for the hundredth time is a weaker check.

What Anthropic measured

93%

of permission prompts that Claude Code users approved

Source: Anthropic: How we contain Claude across products (read November 6, 2026)

84%

fewer permission prompts once Bash ran in Claude Code's sandbox, in Anthropic's own use

Source: Anthropic: Making Claude Code safer with sandboxing (read November 6, 2026)

83%

of overeager actions that auto mode catches before they run

Source: Anthropic: How we contain Claude across products (read November 6, 2026)

5 to 6%

prompt-injection success against Claude Opus 4.7 after 100 adaptive attempts on Gray Swan's benchmark, against 0.1% on one try

Source: Anthropic: How we contain Claude across products (read November 6, 2026)

The three parts from Anthropic's post on containing Claude, read on 6 November 2026. The last column is our summary of it.
What Anthropic defendsIts defensesHolds when the model is fooled?
The environmentProcess sandbox, virtual machine, filesystem boundary, egress controlYes, within what it covers
The modelSystem prompt, classifiers, probes, trainingNot reliably
The outside content it reachesNarrower tool permissions, such as read-only database accessIt limits the damage

Lock down a coding agent: sandbox, scoped rules, an allowlist

Anthropic's autonomous-coding quickstart, the worked example of the last lesson, writes its guardrails into a settings file before the first session starts. The block below is copied from its client.py with the comments left in. PUPPETEER_TOOLS is defined above it in that file as a list of seven Puppeteer MCP tools. Three defenses sit in it, and a fourth sits in a hook.

security_settings = {
    "sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
    "permissions": {
        "defaultMode": "acceptEdits",  # Auto-approve edits within allowed directories
        "allow": [
            # Allow all file operations within the project directory
            "Read(./**)",
            "Write(./**)",
            "Edit(./**)",
            "Glob(./**)",
            "Grep(./**)",
            # Bash permission granted here, but actual commands are validated
            # by the bash_security_hook (see security.py for allowed commands)
            "Bash(*)",
            # Allow Puppeteer MCP tools for browser automation
            *PUPPETEER_TOOLS,
        ],
    },
}
  1. Turn the sandbox on. Bash commands then run inside an OS-level sandbox, bubblewrap on Linux and Seatbelt on macOS, and autoAllowBashIfSandboxed lets them run without a prompt. Anthropic publishes the runtime as sandbox-runtime.
  2. Scope the file tools to paths under ./, so Read, Write, Edit, Glob and Grep stay inside the project directory.
  3. Set defaultMode to acceptEdits. Edits inside the allowed directories then go through without asking.
  4. Allow Bash(*) only because a hook checks every command against a list of 18 entries, from ls and git to npm and init.sh.
  5. Add checks for the risky entries. In security.py, pkill may stop only node, npm, npx, vite or next, chmod may only add +x, and init.sh must be run by a path such as ./init.sh, not by name alone.

The sandbox covers Bash and what it starts. The SDK's type definitions say the file tools and WebFetch follow permission rules instead, and they list two defaults to know. The sandbox is off until you enable it, on macOS and Linux only. allowUnsandboxedCommands is true, so a command can ask to run outside the sandbox unless you set it to false.

Check every tool call with a hook

The Claude Agent SDK's README defines a hook as a Python function that the Claude Code application, not Claude, calls at set points of the agent loop. The model never decides whether a hook runs, and no prompt can talk its way past one. A PreToolUse hook sees the tool name and its input before the call runs, and it can deny the call with a reason the model reads.

The example below is copied from that README, without the two test queries that follow it. It denies any Bash command that contains foo.sh.

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher

async def check_bash_command(input_data, tool_use_id, context):
    tool_name = input_data["tool_name"]
    tool_input = input_data["tool_input"]
    if tool_name != "Bash":
        return {}
    command = tool_input.get("command", "")
    block_patterns = ["foo.sh"]
    for pattern in block_patterns:
        if pattern in command:
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": f"Command contains invalid pattern: {pattern}",
                }
            }
    return {}

options = ClaudeAgentOptions(
    allowed_tools=["Bash"],
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[check_bash_command]),
        ],
    }
)
  • A list of what to block is the weaker design in our view, because one command can be written many ways. The quickstart's hook works the other way round: it pulls every program out of a command line and blocks the call if any one is missing from its list.
  • A command it cannot parse is blocked too, with the reason "Could not parse command for security validation". When a check cannot decide, it says no.
  • allowed_tools trips people up. The README calls it a permission allowlist: listed tools run without asking, and the rest fall through to permission_mode and can_use_tool. It removes nothing from Claude's toolset. To take a tool away, list it in disallowed_tools.

Tool guardrails in the OpenAI Agents SDK

The OpenAI Agents SDK lesson covers input and output guardrails and needs_approval. For an agent that acts, the piece that matters most is the tool guardrail. It wraps one function tool and runs on every call to it. An input check can skip the call or raise a tripwire, and an output check can replace the result.

The example is copied from the SDK's guardrails guide on GitHub. One check refuses arguments that contain sk-, and the other refuses output that does.

import json
from agents import (
    Agent,
    Runner,
    ToolGuardrailFunctionOutput,
)
from agents.decorators import tool, tool_input_guardrail, tool_output_guardrail


@tool_input_guardrail
def block_secrets(data):
    args = json.loads(data.context.tool_arguments or "{}")
    if "sk-" in json.dumps(args):
        return ToolGuardrailFunctionOutput.reject_content(
            "Remove secrets before calling this tool."
        )
    return ToolGuardrailFunctionOutput.allow()


@tool_output_guardrail
def redact_output(data):
    text = str(data.output or "")
    if "sk-" in text:
        return ToolGuardrailFunctionOutput.reject_content("Output contained sensitive data.")
    return ToolGuardrailFunctionOutput.allow()


@tool(
    tool_input_guardrails=[block_secrets],
    tool_output_guardrails=[redact_output],
)
def classify_text(text: str) -> str:
    """Classify text for internal routing."""
    return f"length:{len(text)}"


agent = Agent(name="Classifier", tools=[classify_text])
result = Runner.run_sync(agent, "hello world")
print(result.final_output)

Scope is the catch. Agent-level input guardrails run only for the first agent in a chain, which is why the guide points to tool guardrails for checks that must hold after a handoff. Hosted tools such as WebSearchTool and HostedMCPTool skip the pipeline, and so do ShellTool, ApplyPatchTool, ComputerTool and LocalShellTool. Guard those with approvals or with where they run. By the human-in-the-loop guide, approval for a local shell or file patch is opt-in, with needs_approval false by default.

Prompt injection and the lethal trifecta

OpenAI's guide to safety in building agents defines prompt injection as untrusted text or data entering an AI system, with malicious contents that try to override its instructions. Untrusted means anything you did not write yourself. A web page counts, and so do an email, an issue comment and a tool result.

Simon Willison named the dangerous mix in June 2025: the lethal trifecta. An agent with access to private data, exposure to untrusted content and a way to send data out can be tricked into leaking that data. Take away any one of the three and this attack has no path. We would start with the way out, since few agents need to reach every host.

The way out is wider than an allowlist suggests. Claude Cowork's egress allowlist let traffic to api.anthropic.com through, because the product needs Anthropic's API. A file planted in a user's workspace carried hidden instructions and an attacker's API key. Claude read other files and uploaded them through Anthropic's Files API, into the attacker's account. Anthropic's conclusion is that every function reachable through any domain on an allowlist is now an attack surface.

Content also arrives through tools you trust. A GitHub connector can load a poisoned README straight into the model's context even after it passes malware checks. OpenAI wrote its guide for Agent Builder, which it shuts down on 30 November 2026, and the advice fits any agent:

  • Pass untrusted input in user messages, never in developer messages, which take precedence.
  • Put structured outputs between steps, such as enums and fixed schemas, so no free text is left to smuggle instructions through.
  • Keep approvals on for MCP tools, for reads as well as writes.
  • Pull only the fields you need out of outside input, so untrusted data never directly drives what the agent does.

The guide ends on its limit. Structured outputs and isolation greatly reduce this risk, and they do not fully remove it.

What never to let an agent do alone

This is our list of actions to keep behind a hard limit or a person, each tied to a failure or a setup in the sources above. One question decides each row. If the model were fooled right now, what could this action do?

Our list. Its sources: Anthropic on containing Claude, Claude Code sandboxing, the quickstart, OpenAI's human-in-the-loop guide and the C compiler post.
ActionGuardrailFrom
Read cloud credentials or SSH keysKeep them outside the sandbox, so there is nothing to readAnthropic's red-team exercise
Send data to a host it has not usedAn egress allowlist and a proxy that asks about each new domainClaude Code sandboxing
Push to a git remoteA proxy that checks a scoped credential and the target branch, then adds the real tokenClaude Code on the web
Run any shell commandAn allowlist hook that blocks what it cannot parseThe autonomous-coding quickstart
Cancel an order or send an emailneeds_approval, with a person who approves or rejects the callOpenAI Agents SDK
Run with every prompt offA container with nothing worth stealing in itThe C compiler post

What Simon Willison and Boris Cherny posted

Simon Willison, co-creator of Django and creator of Datasette, posted the lethal trifecta on X on 16 June 2025, quoted below. It speaks to anyone who uses AI agents, which the post defines as LLMs that call tools, so the warning covers a chat you wire tools into as much as an agent you build.

Boris Cherny, who created Claude Code, posted about auto mode on 16 April 2026, the day Opus 4.7 launched and Anthropic extended auto mode to Max users. His post ties it to Opus 4.7. Anthropic's post on how it built auto mode is dated 25 March 2026. In Anthropic's post on containing Claude, permission prompts go to a model-based classifier, and a command it judges safe is approved without asking. Read it next to the chart above. The classifier cuts the prompts a person sees, and the sandbox is what holds when it misses.

Any time you combine access to private data with exposure to untrusted content and the ability to externally communicate an attacker can trick the system into stealing your data

Simon Willison, Co-creator of Django, creator of Datasette · X (read November 6, 2026)
Simon Willison (@simonw) on X, 16 June 2025: If you use "AI agents" (LLMs that call tools) you need to be aware of the Lethal Trifecta. Any time you combine access to private data with exposure to untrusted content and the ability to externally communicate, an attacker can trick the system into stealing your data.
Simon Willison on X, 16 June 2025
Boris Cherny (@bcherny) on X, 16 April 2026: 1/ Auto mode = no more permission prompts. Opus 4.7 loves doing complex, long-running tasks like deep research, refactoring code, building complex features, iterating until it hits a performance benchmark. The post continues past Show more.
Boris Cherny on X, 16 April 2026

Screenshots of X's own embeds of the posts, taken on 6 November 2026, with the time in UTC. Select one to open the post on X. X cuts both posts short with Show more.

Common mistakes

  • Writing "never read .env" in the system prompt and calling it a guardrail.
  • Allowing Bash(*) with no hook behind it.
  • Listing a tool in allowed_tools and expecting the others to disappear.
  • Leaving API keys or cloud credentials where the agent can read them.
  • Adding a domain to the allowlist without asking what else that domain lets the agent do.
  • Pasting web pages, emails or tool output into the developer message.
  • Showing so many approval prompts that nobody reads them.
  • Running --dangerously-skip-permissions on your own machine.

Try it yourself

  • Run /sandbox in Claude Code, then ask for a command that reaches the network and see what happens.
  • Copy the README hook, add rm -rf to block_patterns and ask the agent to clear a scratch folder.
  • Put a line in a test repository's README that tells the agent to print an environment variable. Ask a sandboxed agent to summarize the repository and check whether it obeyed.
  • Attach block_secrets to a tool, call it with an argument that contains sk-test and read what the model gets back.

Next course: AI security

That closes the harness engineering course. The next course, AI security, opens with prompt injection: instructions hidden in a web page, an email or a file that turn your agent against you, and the designs that limit the damage. The AI evals course after it measures what your guardrails catch.

Guardrails need the same measuring. Count the safe commands your hook blocks and the risky actions it lets through. Anthropic reports the same two rates for auto mode: benign commands blocked, and overeager actions let through. Count again each time you change a rule.

Check yourself

  1. In Anthropic's February 2026 red-team exercise, what would have stopped the stolen credentials from leaving?

    Show answer

    B. Egress controls, and a filesystem boundary that keeps ~/.aws out of reach The instruction came through the user, so the model layer had nothing to flag. Only limits in the environment hold in that case.

  2. What does allowed_tools do in the Claude Agent SDK?

    Show answer

    D. Lets listed tools run without asking, while the rest fall through to the permission mode It is a permission allowlist and removes nothing from the toolset. A tool is taken away with disallowed_tools.

  3. Which three things make Simon Willison's lethal trifecta?

    Show answer

    A. Private data, untrusted content and a way to send data out An agent that has all three can be tricked into leaking the data. Remove one and that attack has no path.

  4. What does the quickstart's Bash hook do with a command it cannot parse?

    Show answer

    C. Blocks it It fails closed, with the reason "Could not parse command for security validation".

Frequently asked questions

What are AI agent guardrails?

Anything that limits or checks an agent's actions, from a sandbox and an egress allowlist to a hook, a tool guardrail and a person who approves a call. Sort them by one test: does the limit still hold if the model is fooled?

Is --dangerously-skip-permissions safe to use?

Only somewhere you can throw away. Nicholas Carlini ran his compiler agents with it and told readers to use a container, not their own machine. Keep credentials out of that container and limit where it can connect.

Can a classifier stop prompt injection?

It lowers the rate. Anthropic writes that any probabilistic defense has a non-zero miss rate. Both incidents it learned most from were data leaving through a permitted path, where the model saw nothing unusual.

What is the difference between a permission rule and a sandbox?

The agent harness checks a permission rule against the tool call as written, before the tool runs. The operating system enforces a sandbox on Bash and every process it starts, so a script that opens a file on its own still hits the wall.

Is Claude Code auto mode safe without a sandbox?

Anthropic does not present it that way. Its classifier blocks roughly 0.4% of benign commands and lets about 17% of overeager actions through, so the post calls it one layer of defense inside a sandbox, not a substitute for one.

Sources

  1. Anthropic: How we contain Claude across products (read November 6, 2026)https://www.anthropic.com/research/how-we-contain-claude
  2. Anthropic: Making Claude Code safer with sandboxing (read November 6, 2026)https://www.anthropic.com/research/claude-code-sandboxing
  3. GitHub: anthropics/sandbox-runtime (read November 6, 2026)https://github.com/anthropics/sandbox-runtime
  4. Anthropic Engineering: Building a C compiler with a team of parallel Claudes (read November 6, 2026)https://www.anthropic.com/research/building-c-compiler
  5. GitHub: anthropics/claude-quickstarts, autonomous-coding README (read November 6, 2026)https://github.com/anthropics/claude-quickstarts/tree/main/autonomous-coding
  6. GitHub: autonomous-coding client.py security settings (read November 6, 2026)https://github.com/anthropics/claude-quickstarts/blob/main/autonomous-coding/client.py
  7. GitHub: autonomous-coding security.py command allowlist (read November 6, 2026)https://github.com/anthropics/claude-quickstarts/blob/main/autonomous-coding/security.py
  8. GitHub: anthropics/claude-agent-sdk-python README (read November 6, 2026)https://github.com/anthropics/claude-agent-sdk-python
  9. GitHub: claude-agent-sdk-python types.py (read November 6, 2026)https://github.com/anthropics/claude-agent-sdk-python/blob/main/src/claude_agent_sdk/types.py
  10. GitHub: openai/openai-agents-python, guardrails guide (read November 6, 2026)https://github.com/openai/openai-agents-python/blob/main/docs/guardrails.md
  11. GitHub: openai/openai-agents-python, human-in-the-loop guide (read November 6, 2026)https://github.com/openai/openai-agents-python/blob/main/docs/human_in_the_loop.md
  12. OpenAI Developers: Safety in building agents (read November 6, 2026)https://developers.openai.com/api/docs/guides/agent-builder-safety
  13. Anthropic: How we built Claude Code auto mode (read November 6, 2026)https://www.anthropic.com/research/claude-code-auto-mode
  14. Anthropic: Introducing Claude Opus 4.7 (read November 6, 2026)https://www.anthropic.com/news/claude-opus-4-7
  15. X: Simon Willison on the lethal trifecta (read November 6, 2026)https://x.com/simonw/status/1934602159984984235
  16. X: Boris Cherny on Claude Code auto mode (read November 6, 2026)https://x.com/bcherny/status/2044847849662505288

Share this lesson

Check an MCP server before your agent uses it

Read the checks Mindket runs before a server joins an agent's tools, from who publishes it to what it can reach.

Are MCP servers safe?

This lesson as Markdown · Harness engineering: build the code around an AI agent · All courses