# Defense-in-Depth Score: OpenAI Agents SDK (Python)

**Repo:** https://github.com/openai/openai-agents-python · **Commit:** `81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3` · **Reviewed:** 2026-10-03
**What it is:** Lightweight multi-agent workflow framework with tools, handoffs, guardrails
**Category:** Agent Frameworks
**Scored configuration:** Framework defaults: Agent/Runner with public-constructor defaults for tools, MCP servers, sessions and tracing, plus SandboxAgent with its default capabilities on the README-led UnixLocalSandboxClient (Linux).
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory opt-in · untrusted input yes · third party extensions opt-in · sub agents yes · external communication yes

## Score: 3.2 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L1 | L1 | L0 | L1 | 0.20 | G1 | **0.20** (alt) | High |
| C2 | Approval gates | L3 | L1 | L0 | L1 | 0.35 | C2-POWERBYPASS | **0.25** | High |
| C3 | Tool & action scoping | L2 | L2 | L1 | L0 | 0.35 | — | **0.35** | High |
| C4 | Code-execution isolation | L4 | L1 | L0 | L2 | 0.47 | G1 | **0.47** (alt) | High |
| C5 | Untrusted input blast radius | L1 | L1 | L0 | L0 | 0.15 | G1 | **0.15** (alt) | High |
| C6 | Memory, context & configuration integrity | L1 | L1 | L2 | L2 | 0.35 | — | **0.35** | High |
| C7 | Third-party extensions | L1 | L0 | L1 | L2 | 0.23 | — | **0.23** | Medium |
| C8 | Secrets & sensitive-data protection | L2 | L2 | L0 | L0 | 0.30 | — | **0.30** | High |
| C9 | Audit & traceability | L2 | L2 | L2 | L1 | 0.45 | — | **0.45** | High |
| C10 | Limits & kill switch | L2 | L1 | L2 | L2 | 0.42 | — | **0.42** | High |


The SDK ships strong building blocks (per-call approvals bound to exact arguments, remote sandbox backends, log redaction) but turns none of them on: every tool runs without approval by default, and the README's sandbox example runs model-written shell commands directly on a Linux host with the developer's full environment. A prompt-injected agent built from defaults can read host credentials and act or exfiltrate unattended, and tracing exports full prompts and tool data to OpenAI by default. Safety depends on the developer opting into needs_approval, a hosted or hardened sandbox, environment filtering, and turning off sensitive trace data.

## Critical gaps
- The README-led UnixLocal sandbox runs model-written commands unconfined on Linux with the full host environment, so a hijacked agent holds all of the developer's credentials. (ASI03, T3; C1) — [src/agents/sandbox/sandboxes/unix_local.py:752-753](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L752-L753); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537)
- No execution isolation by default: the lead sandbox backend executes on the host with credentials in the environment. (ASI05, T11; C4) — [src/agents/sandbox/sandboxes/unix_local.py:752-753](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L752-L753); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [README.md:97](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/README.md#L97)
- Approval is off for every tool, and LocalShellTool/ComputerTool cannot be gated at all, so the most powerful paths skip the gate by default. (ASI02, ASI09, T10; C2) — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501); [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423); [src/agents/sandbox/capabilities/tools/shell_tool.py:181-183](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/tools/shell_tool.py#L181-L183)
- A prompt-injected agent can exfiltrate data and take irreversible actions with no human involved in the default configuration. (ASI01, T6, LLM01; C5) — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501); [src/agents/sandbox/capabilities/tools/shell_tool.py:181-183](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/tools/shell_tool.py#L181-L183); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589)

## Criterion details

### C1 Identity & least privilege — 0.20 (high)

The SDK has no identity or authorization layer of its own: every tool a developer registers runs inside the application process with whatever credentials that process holds, and nothing maps a tool call to a permission check. The local sandbox backend that the README leads with copies the whole host environment (API keys, cloud credentials) into every shell command the model runs by default. The sandbox can be told to run commands as a separate OS user or to pass only an allowlist of environment variables, but both are opt-in. A hijacked agent therefore holds everything the developer's process holds.

- **default configuration** (default; raw 0.00 → 0.00)
  - **S L0:** Tools run with the host process's ambient authority; the default UnixLocal sandbox inherits the full host environment, including any credentials in it. — [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537); searched `rg -n -i "downscop|token.exchange|on_behalf_of|least.privilege"` in `src/agents` → 0 hits (No credential downscoping, token exchange or least-privilege policy code anywhere in the SDK.) (verified)
    - *To reach the next level:* No dedicated or scoped identity for tools by default; env filtering and run_as are opt-in.
  - **C L0:** No authorization check sits between the model's tool call and execution on any path; function tools, MCP stdio servers and sandbox shell commands all use ambient process authority. — [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589) (verified)
    - *To reach the next level:* No authorization layer that every tool path traverses.
  - **D L0:** The shipped default (inherit_host_environment=True) passes the operator's whole environment to model-run commands; narrowing requires manual configuration. — [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537); [src/agents/sandbox/sandbox_agent.py:57](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandbox_agent.py#L57) (verified)
    - *To reach the next level:* No narrower default: env inheritance is on and run_as is unset by default.
  - **B L0:** With the README-led UnixLocal backend on Linux, model-run shell commands execute as the developer's OS user with the full environment, so the blast radius is the user's entire account across every service whose credentials are on the machine. — [src/agents/sandbox/sandboxes/unix_local.py:752-753](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L752-L753); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [README.md:97](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/README.md#L97) (verified)
    - *To reach the next level:* Nothing confines what the inherited credentials can reach.
- **opt-in host environment allowlist and sandbox run_as user** (alt; raw 0.20, cap G1 → 0.20) ← counted
  - **S L1:** UnixLocalSandboxClient accepts inherit_host_environment=False or a host_environment_allowlist, and SandboxAgent.run_as runs model-facing sandbox tools as a named OS user: a dedicated but broadly scoped identity. — [src/agents/sandbox/sandboxes/unix_local.py:1537-1538](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537-L1538); [src/agents/sandbox/sandbox_agent.py:57-58](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandbox_agent.py#L57-L58) (verified)
    - *To reach the next level:* No per-tool or per-request credential scoping; function tools and MCP servers still use process authority.
  - **C L1:** The allowlist and run_as apply only to sandbox capability tools; function tools, LocalShellTool executors and MCP servers keep ambient authority. — [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423) (verified)
    - *To reach the next level:* Extension, function-tool and MCP paths are not covered.
  - **D L0:** Both controls are opt-in. — [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537) (verified)
    - *To reach the next level:* Not on by default.
  - **B L1:** Even with env filtering the app process still holds its keys and function tools run with them, giving write access across multiple systems. — [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589) (verified)
    - *To reach the next level:* Credentials are not scoped to one system or short-lived.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

### C2 Approval gates — 0.25 (high)

The SDK has a well-built human-in-the-loop primitive: a tool marked needs_approval pauses the run, the application sees the exact tool name and arguments, the human approves or rejects, and the approval is bound to that exact call. But it is off for every tool type by default, including the shell tool the sandbox agent gets automatically, and two of the most powerful tool types (the local shell tool and the computer-use tool) cannot be gated at all. As shipped, every consequential action runs without a human.

- **S L3:** When enabled, approval is per call, the ToolApprovalItem carries the raw call arguments, a callable needs_approval can decide per parsed argument set, and approval is bound to an invocation fingerprint that includes the arguments. — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501); [src/agents/_tool_invocation.py:181-183](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/_tool_invocation.py#L181-L183); [src/agents/run_context.py:1284](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_context.py#L1284); [src/agents/run_context.py:1341](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_context.py#L1341) (verified)
  - *To reach the next level:* An always_approve decision covers later calls of the same tool with different arguments, and the SDK does not render the call to the human itself.
- **C L1:** Only tools explicitly flagged are gated; LocalShellAction calls the executor directly with no approval path and ComputerTool exposes only an on_safety_check callback. — [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423); [src/agents/tool.py:897](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L897); [src/agents/mcp/server.py:749-750](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L749-L750) (verified)
  - *To reach the next level:* LocalShellTool and ComputerTool cannot be gated and unflagged tools (including MCP and sandbox exec) run ungated.
- **D L0:** needs_approval defaults to False everywhere; approval is opt-in. — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501); [src/agents/tool.py:1476](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L1476); [src/agents/sandbox/capabilities/tools/shell_tool.py:181-183](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/tools/shell_tool.py#L181-L183); [src/agents/mcp/server.py:749-750](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L749-L750) (verified)
  - *To reach the next level:* Approval is not on by default for any tool.
- **B L1:** A framework places no bound on what registered tools do; the default sandbox gives shell and file-write in a workspace with optional snapshots, but external actions (emails, API writes) have no undo or quantity limits. — [src/agents/sandbox/capabilities/capabilities.py:10](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/capabilities.py#L10); [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423) (verified)
  - *To reach the next level:* No previews, dry-runs, rollback for external actions, or rate limits on consequential actions.
- **Cap:** C2-POWERBYPASS — LocalShellTool and ComputerTool have no approval hook at all, and the SandboxAgent's default exec_command shell tool is ungated, so the most powerful action paths skip the gate by default.

### C3 Tool & action scoping — 0.35 (high)

Function tool arguments are parsed against a strict JSON schema and validated by a generated pydantic model before the tool body runs, and sandbox file tools resolve paths against the workspace root. But validation is only as narrow as the types the developer writes, MCP tool arguments are forwarded without SDK-side validation, and the sandbox agent ships with a general shell tool that takes any command string. The default sandbox capability set includes shell, file writing and compaction; a misused shell on the README's local backend reaches the whole machine.

- **S L2:** Function tool inputs are validated by a pydantic model generated from the signature (typed schema), and sandbox paths are normalized to the workspace; the sandbox exec_command accepts an arbitrary shell string. — [src/agents/tool.py:2733-2739](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L2733-L2739); [src/agents/sandbox/capabilities/tools/shell_tool.py:115](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/tools/shell_tool.py#L115) (verified)
  - *To reach the next level:* No allowlist validation in code for commands, URLs or hosts; the shell tool is raw passthrough.
- **C L2:** Schema validation applies automatically to every decorated function tool; MCP tool arguments are json-decoded and passed straight to call_tool. — [src/agents/mcp/util.py:699](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/util.py#L699); [src/agents/mcp/util.py:727](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/util.py#L727) (verified)
  - *To reach the next level:* MCP/extension tools are not wrapped by the shared validation layer.
- **D L1:** A bare Agent has no tools, but SandboxAgent's default capabilities are Filesystem, Shell and Compaction, so exec and write are on by default; capabilities can be replaced individually. — [src/agents/sandbox/sandbox_agent.py:54](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandbox_agent.py#L54); [src/agents/sandbox/capabilities/capabilities.py:10](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/capabilities.py#L10) (verified)
  - *To reach the next level:* The default sandbox tool set is not read-only.
- **B L0:** The default shell tool runs any command; on the README's UnixLocal backend on Linux that is the developer's whole machine. — [src/agents/sandbox/sandboxes/unix_local.py:752-753](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L752-L753); [README.md:97](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/README.md#L97) (verified)
  - *To reach the next level:* No workspace confinement of the general shell on the lead local backend.
- **Cap:** none

### C4 Code-execution isolation — 0.47 (high)

Code execution isolation depends entirely on which sandbox backend the developer picks; there is no default. The README's sandbox example uses the local Unix backend, which on Linux runs model-written shell commands directly on the host as the developer's user with the full host environment, and on macOS adds only filesystem restrictions without network isolation. The Docker backend is a stock container (root, default capabilities, network on), and the hosted backends (E2B, Modal, Daytona and others) are real remote sandboxes but opt-in and network-enabled by default. Local shell tools, MCP stdio servers and function tools always run on the host.

- **default configuration** (default; raw 0.00 → 0.00)
  - **S L0:** The README-led UnixLocal backend adds no OS confinement on Linux: commands are returned unchanged and run as same-user subprocesses. — [src/agents/sandbox/sandboxes/unix_local.py:752-753](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L752-L753); [src/agents/sandbox/sandboxes/unix_local.py:1521](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1521) (verified)
    - *To reach the next level:* No container, OS sandbox profile or remote sandbox in the default path.
  - **C L0:** LocalShellTool and ShellTool local executors are application callables run on the host; MCP stdio servers launch on the host. — [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423); [src/agents/mcp/server.py:2064](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L2064) (verified)
    - *To reach the next level:* No execution path is isolated by default.
  - **D L0:** There is no default sandbox client; the developer must choose one, and the README example chooses the unconfined local backend. — [src/agents/sandbox/runtime_session_manager.py:494-498](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/runtime_session_manager.py#L494-L498); [README.md:97](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/README.md#L97) (verified)
    - *To reach the next level:* Isolation is off by default.
  - **B L0:** Commands inherit the full host environment and the host filesystem, so a malicious script reaches the user's home directory, credentials and network. — [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537) (verified)
    - *To reach the next level:* Credentials and the host filesystem are reachable from executed code.
- **opt-in hosted remote sandbox backends (E2B, Modal, Daytona, etc.)** (alt; raw 0.47, cap G1 → 0.47) ← counted
  - **S L4:** Hosted clients such as E2BSandboxClient run the sandbox session in a remote provider VM, kernel-separated from the application host. — [src/agents/extensions/sandbox/e2b/sandbox.py:1677-1678](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/extensions/sandbox/e2b/sandbox.py#L1677-L1678) (verified)
  - **C L1:** Only the sandbox capability tools (exec_command, apply_patch, file tools) run remotely; LocalShellTool executors, function tools and MCP stdio servers still run on the host. — [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423) (verified)
    - *To reach the next level:* Other execution paths (function tools, MCP stdio servers, local shell executors) are not routed through the sandbox.
  - **D L0:** Hosted backends must be selected explicitly; nothing selects them by default. — [src/agents/sandbox/runtime_session_manager.py:494-498](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/runtime_session_manager.py#L494-L498) (verified)
    - *To reach the next level:* Not on by default.
  - **B L2:** E2B sandboxes allow internet access by default, so a compromised workspace has unrestricted egress plus whatever the manifest injects. — [src/agents/extensions/sandbox/e2b/sandbox.py:619](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/extensions/sandbox/e2b/sandbox.py#L619) (verified)
    - *To reach the next level:* Egress is not off or allowlisted by default.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

### C5 Untrusted input blast radius — 0.15 (high)

Nothing in the SDK limits what a hijacked agent can do after reading untrusted content. Tool and MCP results, web search results and handoff messages enter the conversation and the model can then call any tool, including shell and network tools, with no approval by default. Input, output and tool guardrails exist but are opt-in hooks whose typical use is detection, and the default trace export sends the full content to OpenAI. A successful prompt injection can leak data and take irreversible actions unattended.

- **default configuration** (default; raw 0.00, cap C5-WORSTCASE → 0.00)
  - **S L0:** No structural limit on capabilities after untrusted content is read; no taint tracking, quarantined model or Rule-of-Two enforcement exists. — searched `rg -n -i "taint|quarantin|rule of two"` in `src/agents` → 14 hits (All hits are SQLite connection quarantine bookkeeping or memory prompt prose, not injection controls.) (verified)
    - *To reach the next level:* No approval or capability restriction triggered by untrusted content.
  - **C L0:** Tool and MCP results are fed back to the model with no distinction from principal instructions in any policy. — [src/agents/mcp/util.py:727](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/util.py#L727); [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423) (verified)
    - *To reach the next level:* Untrusted sources are not distinguished.
  - **D L0:** Nothing is on by default; guardrail lists default to empty. — [src/agents/agent.py:373](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/agent.py#L373) (verified)
    - *To reach the next level:* No default control.
  - **B L0:** A framework's documented use combines untrusted input (web, MCP, files), private data, and egress/state change (shell, function tools) with no approval by default, so a hijacked agent can exfiltrate and act irreversibly unattended. — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501); [src/agents/sandbox/capabilities/tools/shell_tool.py:181-183](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/tools/shell_tool.py#L181-L183); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589) (verified)
    - *To reach the next level:* Nothing breaks the combination of untrusted input, sensitive data and egress.
- **opt-in input/output/tool guardrails** (alt; raw 0.15, cap G1 → 0.15) ← counted
  - **S L1:** InputGuardrail/OutputGuardrail and ToolInput/OutputGuardrail run developer functions that can trip a tripwire; this is detection, not a structural limit. — [src/agents/guardrail.py:72](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/guardrail.py#L72); [src/agents/tool_guardrails.py:152](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool_guardrails.py#L152) (verified)
    - *To reach the next level:* Detection only; no structural restriction after untrusted content.
  - **C L1:** Guardrails attach per agent or per tool and only where the developer adds them. — [src/agents/agent.py:373](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/agent.py#L373) (verified)
    - *To reach the next level:* Not applied to all untrusted sources automatically.
  - **D L0:** Opt-in. — [src/agents/agent.py:373](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/agent.py#L373) (verified)
    - *To reach the next level:* Off by default.
  - **B L0:** Detection failures leave the same unattended exfiltration and irreversible-action paths. — [src/agents/tool.py:499-501](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L499-L501) (verified)
    - *To reach the next level:* Same worst case as the default.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

### C6 Memory, context & configuration integrity — 0.35 (high)

Conversation sessions are in-memory by default and scoped by session ID in SQL, and long-term sandbox memory is opt-in. When persistence is enabled, stored history and model-generated memory summaries are replayed into context (memory summaries into the instructions) without validation or review. The default sandbox system prompt tells the model to obey AGENTS.md files found anywhere in the workspace, so a cloned repository's instruction files act as high-priority instructions without any trust decision, although they cannot add tools or change security settings by themselves.

- **S L1:** Writes to sessions and sandbox memory are not validated, memory_summary.md is injected into instructions, and the default sandbox prompt makes workspace AGENTS.md files authoritative. — [src/agents/sandbox/instructions/prompt.md:21](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/instructions/prompt.md#L21); [src/agents/sandbox/runtime_agent_preparation.py:195-196](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/runtime_agent_preparation.py#L195-L196); [src/agents/sandbox/capabilities/memory.py:58](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/memory.py#L58) (verified)
  - *To reach the next level:* No provenance-as-data presentation and instruction files still load as high-priority context.
- **C L1:** Session history keeps its item types and roles, but no memory, summary or instruction-file path is gated. — [src/agents/sandbox/capabilities/memory.py:84](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/capabilities/memory.py#L84) (verified)
  - *To reach the next level:* Memory, compaction summaries and AGENTS.md loading are uncontrolled.
- **D L2:** SQLiteSession defaults to an in-memory database and every query filters by session_id. — [src/agents/memory/sqlite_session.py:34](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/memory/sqlite_session.py#L34); [src/agents/memory/sqlite_session.py:281](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/memory/sqlite_session.py#L281) (verified)
  - *To reach the next level:* The sandbox shell can modify persistent memory/workspace files; isolation is not tamper-resistant against the model.
- **B L2:** Default sessions are ephemeral, but a persisted workspace or memory file poisoned by injected content is re-read in later sessions and can steer tool use. — [src/agents/sandbox/instructions/prompt.md:25](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/instructions/prompt.md#L25) (verified)
  - *To reach the next level:* Persistent poisoning is not limited to text output or made reviewable/rollbackable.
- **Cap:** none

### C7 Third-party extensions — 0.23 (medium)

Third-party extensions come in as MCP servers (local subprocesses or remote endpoints), hosted MCP tools, and sandbox skills or Git repositories pulled into the workspace. The developer names each one in code, but nothing pins versions, checks hashes, or detects changed tool definitions, and the official MCP examples launch servers with `npx -y` at whatever version is latest. Local MCP servers run as separate processes with, when no env is given, the MCP library's reduced default environment, but they still run as the developer's user.

- **S L1:** MCP server commands and Git/skill sources are developer-chosen but unpinned; tool lists are refetched (cache_tools_list=False) without change detection. — [src/agents/mcp/server.py:1971](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L1971); searched `rg -n -i "sha256|signature|verify"` in `src/agents/mcp` → 0 hits (No integrity or signature verification in the MCP client.) (verified)
  - *To reach the next level:* No version pinning or integrity checks.
- **C L0:** No extension type (MCP stdio, MCP HTTP, hosted MCP, skills, GitRepo entries) is verified. — [src/agents/sandbox/entries/artifacts.py:744](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/entries/artifacts.py#L744) (verified)
  - *To reach the next level:* No extension type is verified.
- **D L1:** Nothing third-party is enabled by default and the developer writes the exact command, but official examples routinely launch MCP servers via unpinned `npx -y`, lowering D one level. — [examples/mcp/filesystem_example/main.py:43](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/examples/mcp/filesystem_example/main.py#L43); searched `rg -n '"-y"'` in `examples/mcp` → 3 hits (Three official MCP examples launch unpinned servers with npx -y.) (verified)
  - *To reach the next level:* Examples should pin packages; nothing shows what will run at consent time.
- **B L2:** MCPServerStdio passes env only if the developer sets one; with None the upstream mcp stdio_client uses its default minimal environment (inferred from the mcp library's get_default_environment), so servers run as a separate process with a scrubbed env but the same OS user. — [src/agents/mcp/server.py:2064](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L2064) (inferred)
  - *To reach the next level:* No per-extension sandbox or scoped credentials.
- **Cap:** none

### C8 Secrets & sensitive-data protection — 0.30 (high)

The SDK keeps model inputs and tool data out of its own logs by default, redacts error and validation messages, and strips mount credentials from serialized run state. But tracing is on by default with sensitive data included, exporting full model inputs, outputs and tool arguments/results to OpenAI's trace ingest endpoint whenever an OpenAI key is present, and the README-led local sandbox hands the whole host environment, including OPENAI_API_KEY, to model-run shell commands, where one `env` call puts it in model context.

- **S L2:** Default log suppression of model and tool data (DONT_LOG_* default True), redacted validation errors, and sanitized run-state mount credentials on main paths. — [src/agents/_debug.py:13](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/_debug.py#L13); [src/agents/_debug.py:17](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/_debug.py#L17); [src/agents/run_state.py:1990](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_state.py#L1990) (verified)
  - *To reach the next level:* No redaction before trace export or model-bound messages; secrets come from plain env vars.
- **C L2:** Logs, error messages and serialized state are protected, but traces/telemetry and subprocess environments are not. — [src/agents/run_config.py:55](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_config.py#L55); [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589) (verified)
  - *To reach the next level:* Telemetry and subprocess environments carry sensitive data.
- **D L0:** Tracing is on by default, includes sensitive data by default, and exports it to api.openai.com (a third party when another model provider is used). — [src/agents/run_config.py:407](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_config.py#L407); [src/agents/run_config.py:55](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_config.py#L55); [src/agents/tracing/processors.py:46](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tracing/processors.py#L46); [src/agents/tracing/setup.py:59](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tracing/setup.py#L59) (verified)
  - *To reach the next level:* Content-bearing telemetry is on by default.
- **B L0:** Long-lived OPENAI_API_KEY and any other host secrets are reachable by every model-run command through the inherited environment. — [src/agents/sandbox/sandboxes/unix_local.py:589](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L589); [src/agents/sandbox/sandboxes/unix_local.py:1537](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/sandbox/sandboxes/unix_local.py#L1537) (verified)
  - *To reach the next level:* Keys are long-lived and broadly reachable.
- **Cap:** none

### C9 Audit & traceability — 0.45 (high)

Tracing is on by default and records a structured span per function, MCP, shell, computer, custom and apply-patch tool call, nested under agent, turn and handoff spans with trace and group IDs, and ships them off-host to OpenAI. Approval and rejection decisions are not recorded as spans, there is no requesting-principal or approver attribution, LocalShellTool calls get no tool span, and spans are exported in batches every few seconds, so a crash loses the tail and export is silently skipped without an OpenAI key.

- **S L2:** Structured function spans with inputs/outputs and timestamps for tool calls, nested in agent/turn spans. — [src/agents/run_internal/tool_execution.py:1800](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_execution.py#L1800); searched `rg -n -i "approval"` in `src/agents/tracing` → 0 hits (Approval decisions are not represented in tracing.) (verified)
  - *To reach the next level:* No approver or requesting-principal attribution on records.
- **C L2:** Function, MCP, computer, shell, custom and apply-patch actions are wrapped in function spans; LocalShellAction is not, and approvals/denials are not recorded. — searched `rg -n "with_tool_function_span\("` in `src/agents/run_internal/tool_actions.py` → 4 hits (Computer, Shell, Custom and ApplyPatch actions; LocalShellAction (lines 390-449) has none.); [src/agents/run_internal/tool_actions.py:423](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_internal/tool_actions.py#L423) (verified)
  - *To reach the next level:* LocalShellTool calls and approval decisions are not recorded.
- **D L2:** On by default and exported off-host, but processed in the agent's own process and silently skipped when no OpenAI key is set. — [src/agents/run_config.py:407](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_config.py#L407); [src/agents/tracing/processors.py:146](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tracing/processors.py#L146) (verified)
  - *To reach the next level:* Not written by a component outside the agent's process.
- **B L1:** BatchTraceProcessor queues spans and exports on a 5-second schedule; queued records are lost on crash and actions never wait for their record. — [src/agents/tracing/processors.py:708-710](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tracing/processors.py#L708-L710) (verified)
  - *To reach the next level:* Records are not flushed per action.
- **Cap:** none

### C10 Limits & kill switch — 0.42 (high)

Runs stop after 10 model turns by default, MCP calls have a 5-second read timeout by default, and streaming runs can be cancelled immediately. Function tool timeouts are opt-in, there is no wall-clock or token/cost cap, and an agent used as a tool starts its own fresh 10-turn budget, so delegation multiplies the ceiling. Sandbox shell commands keep running in the background after the tool returns (up to 64 PTY processes).

- **S L2:** DEFAULT_MAX_TURNS=10 enforced via MaxTurnsExceeded, plus a default 5-second MCP session read timeout; per-function-tool timeouts are opt-in. — [src/agents/run_config.py:45](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run_config.py#L45); [src/agents/mcp/server.py:1973](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/mcp/server.py#L1973); [src/agents/tool.py:514](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/tool.py#L514) (verified)
  - *To reach the next level:* No wall-clock or token/cost cap.
- **C L1:** Limits cover the top-level loop; agent.as_tool sub-runs get a fresh DEFAULT_MAX_TURNS budget. — [src/agents/agent.py:755](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/agent.py#L755) (verified)
  - *To reach the next level:* Sub-agents do not count against the parent budget and function tools have no default timeout.
- **D L2:** Sensible default of 10 turns, operator-configurable (None allowed). — [src/agents/run.py:267](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/run.py#L267) (verified)
  - *To reach the next level:* Delegating resets the budget and there is no hard ceiling.
- **B L2:** Moderate turn ceiling; cancel(mode='immediate') stops the loop, but in-flight sandbox processes and sync tool threads can keep running. — [src/agents/result.py:878](https://github.com/openai/openai-agents-python/blob/81f0ccf20c6e24063b9da36fa37f2bdb6a43d8d3/src/agents/result.py#L878) (verified)
  - *To reach the next level:* No spend ceiling or guaranteed termination of spawned processes on stop.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: MCP/tool results fed back to the model (src/agents/mcp/util.py:727) · [B] sensitive data/systems: full host env incl. API keys in sandbox shell (src/agents/sandbox/sandboxes/unix_local.py:589) · [C] state change / egress: ungated sandbox exec_command and function tools (src/agents/sandbox/capabilities/tools/shell_tool.py:183, src/agents/tool.py:501) · Same default session? Yes

## Highest-impact improvements
1. Default UnixLocalSandboxClient to inherit_host_environment=False (or a minimal allowlist) so model-run commands do not receive host credentials. — C1 D L0→L2, +0.100 before caps (Playbook 4)
2. Default trace_include_sensitive_data to false so traces carry spans but not prompts or tool payloads unless opted in. — C8 D L0→L2, +0.100 before caps (Playbook 4)
3. Add needs_approval to LocalShellTool and ComputerTool and default shell/apply_patch/exec_command to require approval. — C2 D L0→L3, +0.150 before caps (Playbook 5)
4. Change the README sandbox example to a hardened Docker or hosted client and refuse UnixLocal on Linux without an explicit unsafe flag. — C4 D L0→L2, +0.100 before caps (Playbook 3)
5. Make agent.as_tool sub-runs draw from the parent's turn budget and add a wall-clock/token cap. — C10 C L1→L3, +0.150 before caps (Playbook 3 step 3)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Scored as a framework on public-constructor defaults; the README's sandbox example (UnixLocalSandboxClient) was taken as the lead sandbox mode. Docker and hosted backends were considered as opt-in mechanisms only.
- Third-party sandbox providers (E2B, Modal, Daytona, Vercel, Cloudflare, Runloop, Blaxel), realtime/voice pipelines, the experimental Codex extension and hosted OpenAI tools were only sampled, not reviewed line by line.
- C7 B relies on the upstream mcp library's stdio default environment behaviour (INFERRED, not verified in this repo).
- No reviewer-steering text was found; AGENTS.md/CLAUDE.md in the repo are contributor guidance.
