A guardrail is a control that does not depend on the model deciding correctly in the moment. Anthropic's guidance says Claude is inherently resilient to jailbreaks and injected instructions, but that the additional steps it lists strengthen your guardrails — the operative word being strengthen, not replace. As an architect you treat the model's own judgment as one probabilistic layer among several, and you put every must-hold rule in a layer where ordinary code decides.
Start by naming the threat model, because the two documented categories need different controls. In jailbreaks and direct prompt injection the user of your application is the adversary and crafts inputs to bypass your rules. In indirect prompt injection the user is trusted, but Claude reads third-party content on their behalf (an inbound email, a fetched web page, OCR output, a tool result) that carries adversarial instructions. Throttling a hostile user does nothing for the second case; structuring how untrusted content enters the context does nothing for the first.
The layers, and what each can honestly guarantee
| Layer | Typical controls | Nature of the guarantee |
|---|---|---|
| Input | Pre-screen with a lightweight model constrained to a yes/no classification; pattern-based input validation; length and format limits; throttling or banning repeat offenders | Classifier: probabilistic. Format, length and allow-list checks: deterministic. |
| Prompt and context | System prompt that states boundaries and how to refuse; a stated policy that tool and document content is untrusted data; third-party content delivered only in tool_result blocks, labelled with its source, ideally JSON-encoded | Probabilistic. Improves compliance, never guarantees it. |
| Tool and action | Least privilege, sandboxing, deny rules, PreToolUse hooks, approval callbacks | Deterministic where enforced in code before the call runs. |
| Output | Schema-constrained output, grounding and citation checks, PostToolUse redaction, output classifiers | Mixed. A code check on structure is deterministic; a content classifier is not. |
| Process | Monitoring, refusal tracking, red-teaming before launch, audit logging, human review gates (Lesson 4.3) | Detective and corrective, not preventive. |
Two design rules fall out of the table. First, layers should fail independently: a classifier and a system prompt both live inside the LLM world and can be beaten by the same crafted input, so at least one layer must be a different kind of mechanism (code at the tool boundary). Second, every added screen costs latency and money and produces false positives, so spend that budget where the consequence of a miss is highest instead of screening everything equally.
Deterministic enforcement versus prompt-only
The decision rule is about consequence, not importance: if one failure causes harm the business cannot absorb (money moved, data disclosed, an irreversible external action), enforce it in code at the tool boundary. If occasional deviation is tolerable (tone, formatting, verbosity), a prompt instruction is enough and a hook is needless overhead.
In the Agent SDK, hooks are callbacks in your own process. A PreToolUse hook returns a permissionDecision of allow, deny, ask or defer (and can rewrite the call with updatedInput); when several hooks or rules apply, deny takes priority over defer, then ask, then allow. A PostToolUse hook runs after execution and can append context or replace the tool output before the model sees it — useful for redacting sensitive fields, useless for preventing something that already happened.
The permission-evaluation order is where scenario questions hide traps. Per the SDK documentation, checks run as: hooks, then deny rules, then ask rules, then the permission mode, then allow rules, then the canUseTool callback. Four consequences follow:
- A hook
denyapplies even inbypassPermissionsmode, and so do deny rules. Hooks are the mechanism for checks that must run on every call. allowed_toolspre-approves what you list; it does not restrict the rest. Combined withbypassPermissions, unlisted tools such asBashare still approved. To block a tool, usedisallowed_tools(a bare tool name removes it from Claude's context entirely).- Auto-approved calls never reach
canUseTool, so a check placed only in that callback can be silently skipped. - A subagent runs in
bypassPermissionsonly when its parent session does, and then it gets autonomous system access — a reason to keep that mode out of production.
Common exam distractor
Watch for three plausible-but-wrong answers. (1) A stronger system prompt for a requirement that must hold every time. (2) A PostToolUse hook offered to block an action; it fires after the tool has run, so the best it can do is log, redact or flag. (3) A single LLM screen presented as the whole defence against injected instructions. A screen lowers the probability of a successful attack; only least privilege and code-level gates bound the damage when one gets through.
Containing indirect prompt injection: structure, then blast radius
Anthropic's mitigation list for untrusted content is mostly about structure. Put third-party content only in tool_result blocks, never in the system prompt or plain user text, because Claude is trained to treat instructions inside tool results with appropriate skepticism. Say what the content is and where it came from (the body of an email from an unknown sender). State the policy in the system prompt: tool and document content is data to report, not commands to follow. JSON-encode third-party strings so an attacker cannot close a quote or tag and break out. Do not put your instructions inside tool results; send them in a user turn that follows. Where you use the computer-use or browser-use tools, Anthropic additionally runs classifiers over what those tools return.
Then design for the case where all of that fails. Apply least privilege (no secrets Claude does not need, sandboxed tools, narrow scopes), screen tool outputs with a small model constrained by structured outputs so the verdict is a parseable boolean, and red-team the agent with hostile documents before launch. The architect's question is not 'will the injection be detected?' but 'if it is obeyed, what is the worst reachable action?'. An agent that reads untrusted mail and can move money without a gate has a bad answer to that question no matter how good its screening is.
Failure behaviour: fail closed or fail open
Every guardrail can itself fail: a classifier call times out, a hook throws, a policy service is down. Decide the behaviour per action, in advance. Gates on irreversible or high-consequence actions fail closed (no verdict means no execution). Low-risk read-only paths may degrade gracefully, ideally with the degradation logged. The SDK documentation shows the gate default: if a PreToolUse callback times out, the tool call is not run and Claude receives a result saying the hook did not respond; a timed-out UserPromptSubmit hook blocks the prompt rather than letting it through unscreened. Your own guard code needs the same discipline — wrap a classifier call so any exception or malformed verdict maps to deny, not allow.
Refusals need their own handling. When streaming classifiers intervene, the API returns a normal HTTP 200 with stop_reason set to refusal and a stop_details object whose category and explanation can be null. Monitoring built only on error rates will not see it, so track refusals as a separate signal, branch on the stop reason, and either reset the conversation context or retry on a fallback model as the docs describe. Finally, close the loop: analyse outputs for signs of successful injection, record which layer fired, and use that to tune prompts and screens.
Key concept
The model's judgment is a layer, not the control. Put must-hold rules where code runs (a PreToolUse gate, a deny rule, a narrowed tool scope), use probabilistic layers to reduce how often those gates are hit, decide fail-closed versus fail-open per action, and measure each layer so you know which one actually caught what.