Skip to main content
Agent actions can be controlled through two complementary mechanisms: confirmation policy that determine when user approval is required, and security analyzer that evaluates action risk levels. Together, they provide flexible control over agent behavior while maintaining safety.

Confirmation Policy

A ready-to-run example is available here!
Confirmation policy controls whether actions require user approval before execution. They provide a simple way to ensure safe agent operation by requiring explicit permission for actions.

Setting Confirmation Policy

Set the confirmation policy on your conversation:
Available policies:
  • AlwaysConfirm() - Require approval for all actions
  • NeverConfirm() - Execute all actions without approval
  • ConfirmRisky() - Only require approval for risky actions (requires security analyzer)

Custom Confirmation Handler

Implement your approval logic by checking conversation status:

Rejecting Actions

Provide feedback when rejecting to help the agent try a different approach:

Ready-to-run Example Confirmation

Require user approval before executing agent actions:
examples/01_standalone_sdk/04_confirmation_mode_example.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Security Analyzer

Security analyzer evaluates the risk of agent actions before execution, helping protect against potentially dangerous operations. They analyze each action and assign a security risk level:
  • LOW - Safe operations with minimal security impact
  • MEDIUM - Moderate security impact, review recommended
  • HIGH - Significant security impact, requires confirmation
  • UNKNOWN - Risk level could not be determined
Security analyzer work in conjunction with confirmation policy (like ConfirmRisky()) to determine whether user approval is needed before executing an action. This provides an additional layer of safety for autonomous agent operations.

LLM Security Analyzer

A ready-to-run example is available here!
The LLMSecurityAnalyzer is the default implementation provided in the agent-sdk. It leverages the LLM’s understanding of action context to provide lightweight security analysis. The LLM can annotate actions with security risk levels during generation, which the analyzer then uses to make security decisions.

Security Analyzer Configuration

Create an LLM-based security analyzer to review actions before execution:
The security analyzer:
  • Reviews each action before execution
  • Flags potentially dangerous operations
  • Can be configured with custom security policy
  • Uses a separate LLM to avoid conflicts with the main agent

Ready-to-run Example Security Analyzer

Automatically analyze agent actions for security risks before execution:
examples/01_standalone_sdk/16_llm_security_analyzer.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Custom Security Analyzer Implementation

You can extend the security analyzer functionality by creating your own implementation that inherits from the SecurityAnalyzerBase class. This allows you to implement custom security logic tailored to your specific requirements.

Creating a Custom Analyzer

To create a custom security analyzer, inherit from SecurityAnalyzerBase and implement the security_risk() method:
For more details on the base class implementation, see the source code.

Defense-in-Depth Security Analyzer

The problem

Your agent is about to run a tool call. Is it safe? The LLMSecurityAnalyzer asks the model itself — but the model can be manipulated, and encoding tricks can hide dangerous commands from it. You need a layer that does not depend on model judgment: something deterministic, local, and fast.

What this gives you

Three composable analyzers that classify actions at the boundary — before the tool runs, not after. No network calls, no model inference, no extra dependencies. They return a SecurityRisk level; your ConfirmRisky policy decides whether to prompt the user.

Quick start

You must configure both the analyzer and the confirmation policy. Setting an analyzer does not automatically change confirmation behavior.
After this, every agent action passes through the analyzer before execution. HIGH-risk actions trigger a confirmation prompt — the user sees the risk level and can approve or reject before the tool runs. MEDIUM and LOW are allowed. UNKNOWN is confirmed by default (confirm_unknown=True). For security-sensitive environments, lower the threshold to catch more:
You can also require confirmation when any analyzer cannot assess risk:
conversation.execute_tool() bypasses the analyzer and confirmation policy. These analyzers protect agent actions in the conversation loop, not direct tool calls.

Adding the LLM analyzer for deeper coverage

The pattern analyzer catches known threats instantly. The LLM analyzer can catch novel or ambiguous cases. Composing both gives you speed and breadth:
The ensemble takes the worst case across all analyzers. If the pattern analyzer says HIGH and the LLM says LOW, the result is HIGH.

Why it works this way

Two corpora, not one. An agent that runs ls /tmp but thinks “I should avoid rm -rf /” is not flagged — shell patterns only see the ls /tmp that will actually execute. Injection patterns like “ignore all previous instructions” scan everything, because they target the model’s instruction-following regardless of where they appear. Max-severity, not averaging. The analyzers scan the same input — they are correlated, not independent. The highest concrete risk wins. That is simpler and more auditable than probabilistic fusion. UNKNOWN means “I don’t know,” not “safe.” By default, if all analyzers return UNKNOWN the ensemble preserves it, and ConfirmRisky triggers confirmation. If any analyzer returns a concrete level, UNKNOWN results are filtered out. For stricter environments, set propagate_unknown=True so that any single UNKNOWN triggers confirmation regardless of other results. Confirm, don’t block. The analyzers return a risk level. The confirmation policy decides what happens. The analyzer does not prevent execution — it classifies risk for the policy layer to act on. Pair with Docker isolation for stronger safety guarantees.

What this does not do

This is a deterministic action-boundary control. It is not:
  • A complete prompt-injection solution
  • A full shell parser or AST interpreter
  • A sandbox replacement
  • A guarantee against novel threats the patterns do not cover
It is additive to LLMSecurityAnalyzer and GraySwanAnalyzer, not a replacement for either.

Known limitations

Extraction budget and primary-surface-first ordering

The 30k-character cap is applied per scanning corpus, not per field: every field competes for one shared budget (the _BoundedSegments buffer in defense_in_depth/utils.py). That creates a secondary risk — a single oversized field could consume the whole budget and leave higher-value fields unscanned. tool_name has no length validation in the SDK, so a 30k hallucinated name is a real starvation vector, not just a theoretical one. The analyzer addresses this by extraction order, not a per-field cap: the primary attack surface is added first, so it always receives budget even when a later field is adversarially large.
  • Executable corpus: tool_call.arguments (the primary prompt-injection surface) → tool_nametool_call.name.
  • Reasoning corpus: summary (what the agent is about to do) → reasoning_contentthought.
The two corpora are extracted with separate budgets and concatenated without a second outer cap, so a budget-filling arguments payload cannot crowd summary out of the injection scan. Remaining boundary (a strict xfail in the test suite): a payload past 30k characters within a single field is still truncated and invisible. That is the deliberate ReDoS trade-off already listed above; extraction order does not change it.

Configurable Security Policy

A ready-to-run example is available here!
Agents use security policies to guide their risk assessment of actions. The SDK provides a default security policy template, but you can customize it to match your specific security requirements and guidelines.

Using Custom Security Policies

You can provide a custom security policy template when creating an agent:
Custom security policies allow you to:
  • Define organization-specific risk assessment guidelines
  • Set custom thresholds for security risk levels
  • Add domain-specific security rules
  • Tailor risk evaluation to your use case
The security policy is provided as a Jinja2 template that gets rendered into the agent’s system prompt, guiding how it evaluates the security risk of its actions.

Ready-to-run Example Security Policy

Full configurable security policy example: examples/01_standalone_sdk/32_configurable_security_policy.py
Define custom security risk guidelines for your agent:
examples/01_standalone_sdk/32_configurable_security_policy.py
You can run the example code as-is.
The model name should follow the LiteLLM convention: provider/model_name (e.g., anthropic/claude-sonnet-4-5-20250929, openai/gpt-4o). The LLM_API_KEY should be the API key for your chosen provider.
ChatGPT Plus/Pro subscribers: You can use LLM.subscription_login() to authenticate with your ChatGPT account and access Codex models without consuming API credits. See the LLM Subscriptions guide for details.

Next Steps