# Defense-in-Depth Score: Strands Agents (harness-sdk)

**Repo:** https://github.com/strands-agents/harness-sdk · **Commit:** `8ba04f70af542e41aadbc2eb8093b1a07df5ec96` · **Reviewed:** 2026-10-04
**What it is:** AWS's Strands Agents monorepo: Python/TypeScript agent SDKs plus the batteries-included Strands harness (create_harness) and CLI.
**Category:** Agent Frameworks
**Scored configuration:** Python Strands harness create_harness() with all default arguments (the README quick start), running on the host with no sandbox passed.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory yes · untrusted input yes · third party extensions opt-in · sub agents yes · external communication yes

## Score: 2.1 / 10.0 (Minimal)

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


The default harness agent gets a host shell, unrestricted file writes, web fetching and sub-agents, all running on your machine with your full environment and AWS credentials, and every tool call runs without approval. Good controls exist (a per-call approval gate that covers sub-agents and programmatic tool calls, a Docker sandbox option, turn and token budgets), but all are opt-in. The dominant risk is prompt injection from a web page or repository file turning into credential theft or destructive commands with no human in the loop.

## Critical gaps
- The default agent runs with the operator's ambient credentials (AWS default chain and full process environment in every shell command), so a hijack holds the user's whole account. (ASI03, T3, LLM06; C1) — [strands-py/src/strands/models/bedrock.py:256](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/models/bedrock.py#L256); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78)
- Model-generated shell commands run on the host with no isolation and the full process environment by default. (ASI05, T11, LLM05; C4) — [strands-py/src/strands/agent/agent.py:372](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L372); [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78)
- A prompt-injected session can both leak secrets (host shell with full environment, unrestricted web_fetch) and take irreversible actions with no human involved. (ASI01, T6, LLM01; C5) — [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [harness-py/src/strands_harness/tools/web_fetch.py:162](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/web_fetch.py#L162); [harness-py/src/strands_harness/interventions.py:89-90](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/interventions.py#L89-L90)
- Workspace skills auto-load, and their bundled scripts run through the host shell with the agent's full environment and credentials. (ASI04, T17, LLM03; C7) — [harness-py/src/strands_harness/agent.py:151](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L151); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78)

## Criterion details

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

The harness has no identity or authorization layer of its own. The default model client uses the AWS default credential chain, and the shell tool runs commands on the host with the full environment of the process, so anything the operator's credentials, AWS profile, SSH agent or tokens can do, a hijacked agent can do too. Authorization lives only in the system prompt.

- **S L0:** Bedrock is reached through boto3's default credential chain and every shell command inherits the operator's environment; nothing narrows that ambient authority. — [strands-py/src/strands/models/bedrock.py:256](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/models/bedrock.py#L256); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78); searched `rg -n "env="` in `strands-py/src/strands/sandbox/stream_process.py` → 0 hits (the host subprocess is spawned without an env argument, so it inherits the full parent environment) (verified)
  - *To reach the next level:* No dedicated or scoped identity; credentials are ambient.
- **C L0:** No authorization check exists on any tool path; the only rule is a system-prompt sentence. — [harness-py/src/strands_harness/prompt.py:31](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/prompt.py#L31); [harness-py/src/strands_harness/interventions.py:89-90](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/interventions.py#L89-L90) (verified)
  - *To reach the next level:* No code-level authorization layer on the tool path.
- **D L0:** Default install runs with whatever privilege the launching user holds. — [strands-py/src/strands/models/bedrock.py:256](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/models/bedrock.py#L256); [strands-py/src/strands/agent/agent.py:372](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L372) (verified)
  - *To reach the next level:* No narrower default identity; least privilege needs manual hardening.
- **B L0:** A hijacked agent holds the user's whole account across services: AWS default-chain credentials plus a host shell with the full environment. — [strands-py/src/strands/models/bedrock.py:256](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/models/bedrock.py#L256); [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79) (verified)
  - *To reach the next level:* Credentials would need to be scoped to one system or project.
- **Cap:** none

### C2 Approval gates — 0.45 (high)

By default every tool call, including shell and file writes, runs without asking: the approval gate is off unless the developer passes interventions. When enabled, the 'ask' preset shows the exact tool name and JSON arguments for each call and applies to all tools, including MCP tools, sub-agent delegates and the calls made from programmatic_tool_caller. Nothing provides undo or checkpoints by default, so a wrongly allowed shell command can be irreversible.

- **S L3:** When enabled, HumanInTheLoop prompts per call with the exact tool name and JSON input, with allowlists, negation and an optional classifier as tiers. — [strands-py/src/strands/vended_interventions/hitl/hitl.py:215-216](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/vended_interventions/hitl/hitl.py#L215-L216); [strands-py/src/strands/vended_interventions/hitl/hitl.py:129](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/vended_interventions/hitl/hitl.py#L129) (verified)
  - *To reach the next level:* No argument-level policy on parsed arguments (e.g. parsed shell commands) in the human gate.
- **C L3:** Interventions fire on BeforeToolCallEvent in the shared executor, which background tasks and programmatic_tool_caller inner calls also traverse, and sub-agents inherit the policy. — [strands-py/src/strands/tools/executors/_executor.py:215](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/tools/executors/_executor.py#L215); [strands-py/src/strands/tools/executors/_executor.py:74](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/tools/executors/_executor.py#L74); [harness-py/src/strands_harness/tools/programmatic_tool_caller.py:175](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/programmatic_tool_caller.py#L175); [harness-py/src/strands_harness/agent.py:452](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L452) (verified)
  - *To reach the next level:* Allowlists match tool names, not parsed compound shell commands; unknown tools are not rejected by default.
- **D L0:** interventions defaults to None, which resolves to no handlers: approval is opt-in. — [harness-py/src/strands_harness/agent.py:254](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L254); [harness-py/src/strands_harness/agent.py:379](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L379); [harness-py/src/strands_harness/interventions.py:89-90](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/interventions.py#L89-L90) (verified)
  - *To reach the next level:* Approval would need to be on by default.
- **B L0:** With no gate, the default host shell can delete files, push code or call external APIs irreversibly; no checkpoint is on by default. — [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [harness-py/src/strands_harness/defaults.py:25](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L25) (verified)
  - *To reach the next level:* No default checkpoint/rollback for filesystem state.
- **Cap:** G1 — The approval gate exists but interventions defaults to None (off).

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

The default tool set is the broadest possible: a raw shell, unrestricted file write and edit anywhere on the host, a web fetcher and a sub-agent. The file tools only require an absolute path without '..' segments, and web_fetch only checks the URL scheme and characters, with no block on internal or cloud-metadata addresses. The developer can trim the tool list, but nothing is narrowed by default.

- **S L0:** The main tool is a raw shell string; file tools accept any absolute path with no containment. — [strands-py/src/strands/vended_tools/shell/shell.py:54](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/vended_tools/shell/shell.py#L54); [harness-py/src/strands_harness/tools/file_tools.py:25-29](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/file_tools.py#L25-L29) (verified)
  - *To reach the next level:* Arguments would need allowlist validation such as resolved-path containment and host allowlists.
- **C L1:** Only a few tools validate anything (file tools reject relative paths and '..'; web_fetch checks scheme), and web_fetch does not block internal addresses. — [harness-py/src/strands_harness/tools/file_tools.py:25-29](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/file_tools.py#L25-L29); [harness-py/src/strands_harness/tools/web_fetch.py:67](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/web_fetch.py#L67); searched `rg -n -S "169\.254|is_private|ipaddress|localhost"` in `harness-py/src/strands_harness/tools/web_fetch.py` → 0 hits (no check against internal or metadata addresses) (verified)
  - *To reach the next level:* Most built-in tools would need real validation.
- **D L1:** Shell, write, edit, web_fetch, programmatic_tool_caller and subagent are all on by default, but each can be dropped individually via builtin_tools. — [harness-py/src/strands_harness/defaults.py:25](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L25); [harness-py/src/strands_harness/agent.py:90-94](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L90-L94) (verified)
  - *To reach the next level:* Selectable tool groups with a narrower default; ideally a read-only default tool set.
- **B L0:** A misused shell reaches the whole machine and any network host. — [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79) (verified)
  - *To reach the next level:* Tools would need to be scoped to a workspace.
- **Cap:** none

### C4 Code-execution isolation — 0.35 (medium)

The shell tool, file tools and web_fetch run directly on the host through the SDK's NotASandboxLocalEnvironment, whose own docstring says it provides no isolation, and child processes inherit the full environment. A Docker sandbox exists as an option, but it only runs 'docker exec' into a container the developer already started, with no hardening applied by the framework. The programmatic_tool_caller runs model code in the Monty interpreter, which has no host access, but it can call the unisolated shell.

- **default configuration** (default; raw 0.00 → 0.00)
  - **S L0:** Default execution is 'sh -c' on the host with the agent's privileges. — [strands-py/src/strands/agent/agent.py:372](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L372); [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:38-40](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L38-L40) (verified)
    - *To reach the next level:* An OS-level boundary (container, low-privilege user) on the default path.
  - **C L0:** The main shell tool is not sandboxed; only programmatic_tool_caller's guest code runs in Monty. — [harness-py/src/strands_harness/agent.py:90-94](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L90-L94); [harness-py/src/strands_harness/tools/programmatic_tool_caller.py:47](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/programmatic_tool_caller.py#L47) (verified)
    - *To reach the next level:* The main exec tool would need to go through a sandbox.
  - **D L0:** No sandbox by default; isolation is opt-in via Agent(sandbox=...). — [strands-py/src/strands/agent/agent.py:372](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L372) (verified)
    - *To reach the next level:* Isolation on by default.
  - **B L0:** Host-equivalent: full filesystem, network and every credential in the inherited environment. — [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78); searched `rg -n "env="` in `strands-py/src/strands/sandbox/stream_process.py` → 0 hits (the host subprocess is spawned without an env argument, so it inherits the full parent environment) (verified)
    - *To reach the next level:* Workspace-only reach without credentials in the environment.
- **opt-in DockerSandbox (docker exec into a developer-provided container)** (alt; raw 0.35, cap G1 → 0.35) ← counted
  - **S L2:** Commands run via 'docker exec' in a container the developer supplies; user and hardening are whatever that container has. — [strands-py/src/strands/sandbox/docker.py:92](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/docker.py#L92); [strands-py/src/strands/sandbox/docker.py:36-38](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/docker.py#L36-L38) (verified)
    - *To reach the next level:* The framework would need to apply hardening (non-root, dropped capabilities, seccomp, read-only root).
  - **C L2:** Shell, file tools, environment probes and web_fetch (curl transport) route through agent.sandbox, but MCP stdio servers launch on the host. — [harness-py/src/strands_harness/tools/web_fetch.py:162](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/web_fetch.py#L162); [harness-py/src/strands_harness/agent.py:427](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L427) (verified)
    - *To reach the next level:* MCP servers and every other spawned process would need to run in the sandbox.
  - **D L0:** Off by default; the developer must pass a sandbox. — [strands-py/src/strands/agent/agent.py:372](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L372) (verified)
    - *To reach the next level:* On by default.
  - **B L1:** Mounts, network and secrets depend entirely on the developer's container; the framework sets no limits. — [strands-py/src/strands/sandbox/docker.py:36-38](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/docker.py#L36-L38) (inferred)
    - *To reach the next level:* Workspace-only mount, no network egress, resource limits enforced by the framework.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

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

The default agent reads web pages, files and MCP tool results, holds the operator's credentials, and can run shell commands and fetch arbitrary URLs, all in one session with no approval. Nothing tracks whether untrusted content has been read, and the project's AGENTS.md is injected inside a system-reminder block, giving repository text elevated standing. If injected content hijacks the agent, it can exfiltrate secrets and take irreversible actions unattended.

- **S L0:** No structural limit after untrusted content is read; only a system-prompt safety note. — [harness-py/src/strands_harness/prompt.py:31](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/prompt.py#L31); [harness-py/src/strands_harness/interventions.py:89-90](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/interventions.py#L89-L90) (verified)
  - *To reach the next level:* Approval or capability removal once untrusted content enters the session.
- **C L0:** Tool results and repository files enter context with no provenance distinction; AGENTS.md is wrapped as a system-reminder. — [harness-py/src/strands_harness/plugins/environment.py:91](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/plugins/environment.py#L91); [harness-py/src/strands_harness/plugins/environment.py:108](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/plugins/environment.py#L108) (verified)
  - *To reach the next level:* Untrusted sources would need to be distinguished from principal instructions.
- **D L0:** No such control exists to be on by default. — [harness-py/src/strands_harness/agent.py:254](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L254) (verified)
  - *To reach the next level:* An on-by-default taint or approval control.
- **B L0:** A hijacked agent can read credentials via the host shell and send them out via web_fetch or curl, and can delete or push, with no human involved. — [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [harness-py/src/strands_harness/tools/web_fetch.py:162](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/web_fetch.py#L162); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78) (verified)
  - *To reach the next level:* Exfiltration and irreversible actions would need human approval.
- **Cap:** C5-WORSTCASE — Worst case (B L0): a hijacked agent can leak data and take irreversible actions unattended.

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

Long-term memory is on by default: a small model distils 'facts' from the whole conversation, including tool results, into markdown files in ./.agent/memory under the working directory, and they are injected into context on every turn. Because the folder lives in the workspace, a repository can ship pre-written memory, and it also auto-loads ./.agent/skills and the project AGENTS.md. None of these files can change tools or approval settings, but nothing validates or reviews what gets saved or loaded.

- **S L1:** Memory writes are LLM-extracted with no validation and injected every turn (with a source tag); AGENTS.md loads silently as a system-reminder. — [harness-py/src/strands_harness/memory.py:107](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/memory.py#L107); [harness-py/src/strands_harness/memory.py:89](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/memory.py#L89); [strands-py/src/strands/memory/memory_manager.py:749](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/memory/memory_manager.py#L749); [harness-py/src/strands_harness/plugins/environment.py:91](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/plugins/environment.py#L91) (verified)
  - *To reach the next level:* Memory entries presented as data with provenance and gated writes; instruction files not silently elevated.
- **C L0:** No memory, instruction or skill load path is gated or validated. — [harness-py/src/strands_harness/memory.py:107](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/memory.py#L107); [harness-py/src/strands_harness/plugins/environment.py:108](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/plugins/environment.py#L108); [harness-py/src/strands_harness/agent.py:151](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L151) (verified)
  - *To reach the next level:* At least the main memory store would need write control.
- **D L1:** Memory and skills live in directories relative to the working directory, so whatever repository the agent runs in decides what is loaded; delegates get read-only memory. — [harness-py/src/strands_harness/defaults.py:39](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L39); [harness-py/src/strands_harness/defaults.py:37](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L37) (verified)
  - *To reach the next level:* Per-user namespaces enforced outside the workspace.
- **B L0:** Poisoned memory persists across sessions and, when committed to a repository's .agent/memory, reaches every user who runs the harness there; it can steer tool use with no gate. — [harness-py/src/strands_harness/defaults.py:39](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L39); [harness-py/src/strands_harness/memory.py:89](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/memory.py#L89); [harness-py/src/strands_harness/interventions.py:89-90](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/interventions.py#L89-L90) (verified)
  - *To reach the next level:* Poisoned memory would need to be limited to text output or gated actions.
- **Cap:** none

### C7 Third-party extensions — 0.07 (high)

MCP servers are only connected when the developer passes them, but their packages are not pinned or hash-checked, and skills found in ./.agent/skills of the working directory are loaded automatically. Skill scripts run through the same unisolated host shell, with the full environment, when the model follows a skill's instructions. MCP stdio servers get only the environment the config names, but run as the same user on the host.

- **S L1:** Extensions come from user-chosen sources, with no version pinning or integrity check. — [harness-py/src/strands_harness/agent.py:427](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L427); searched `rg -n -S "sha256|digest|integrity"` in `harness-py/src` → 0 hits; searched `rg -n -S "sha256|digest|integrity|signature"` in `strands-py/src/strands/vended_plugins/skills strands-py/src/strands/tools/mcp/mcp_client.py` → 0 hits (verified)
  - *To reach the next level:* Version pinning of MCP servers and skills.
- **C L0:** No extension type is verified. — searched `rg -n -S "sha256|digest|integrity|signature"` in `strands-py/src/strands/vended_plugins/skills strands-py/src/strands/tools/mcp/mcp_client.py` → 0 hits (verified)
  - *To reach the next level:* At least one extension type would need verification.
- **D L0:** Skills in the workspace's ./.agent/skills are loaded automatically with no prompt. — [harness-py/src/strands_harness/agent.py:151](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L151); [harness-py/src/strands_harness/defaults.py:37](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L37) (verified)
  - *To reach the next level:* No workspace-sourced extensions without explicit consent.
- **B L0:** Skill scripts execute through the host shell as the same user with the full process environment. — [strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py:78-79](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/not_a_sandbox_local_environment.py#L78-L79); [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78); [strands-py/src/strands/tools/mcp/mcp_client.py:2655](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/tools/mcp/mcp_client.py#L2655) (verified)
  - *To reach the next level:* Extensions in separate processes with a scrubbed environment.
- **Cap:** none

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

Credentials come from the environment and the AWS default chain, and the shell passes the whole environment to every command, so the model can read keys with a single 'env' call. Tracing is off unless OTEL_TRACES_EXPORTER is set, but when it is on, prompts and tool data are exported unredacted unless redaction is opted into. Session transcripts are stored as plaintext under ./.agent/sessions, with no secret scanning.

- **S L1:** Secrets come from env vars; the only masking is opt-in trace-attribute redaction. — [strands-py/src/strands/telemetry/tracer.py:136](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/telemetry/tracer.py#L136); searched `rg -n -S "redact|secret"` in `harness-py/src` → 2 hits (one comment about SDK guardrail redaction and one OAuth config key mapping; no secret redaction) (verified)
  - *To reach the next level:* Type-level masking and log filters on main paths.
- **C L1:** Only the trace path can be redacted; subprocess environments, transcripts and model-bound messages are unprotected. — [strands-py/src/strands/telemetry/tracer.py:104](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/telemetry/tracer.py#L104); searched `rg -n "env="` in `strands-py/src/strands/sandbox/stream_process.py` → 0 hits (the host subprocess is spawned without an env argument, so it inherits the full parent environment) (verified)
  - *To reach the next level:* Logs and transcripts would need protection.
- **D L2:** Telemetry export is opt-in via OTEL_TRACES_EXPORTER; trace redaction is off by default and transcripts are plaintext. — [harness-py/src/strands_harness/telemetry.py:39-41](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/telemetry.py#L39-L41); [strands-py/src/strands/telemetry/tracer.py:104](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/telemetry/tracer.py#L104); [harness-py/src/strands_harness/defaults.py:35](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L35) (verified)
  - *To reach the next level:* Content redaction always on.
- **B L0:** Long-lived ambient keys in the process environment are reachable by every subprocess and thus by the model. — [strands-py/src/strands/sandbox/stream_process.py:73-78](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/sandbox/stream_process.py#L73-L78); searched `rg -n "env="` in `strands-py/src/strands/sandbox/stream_process.py` → 0 hits (the host subprocess is spawned without an env argument, so it inherits the full parent environment); [strands-py/src/strands/models/bedrock.py:256](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/models/bedrock.py#L256) (verified)
  - *To reach the next level:* Keys would need to be scoped and kept out of the subprocess environment.
- **Cap:** none

### C9 Audit & traceability — 0.35 (medium)

By default the harness saves a snapshot of the conversation, including tool calls and results, after each message under ./.agent/sessions in the working directory. The snapshot is overwritten each time and the context manager summarizes old turns, so it is not a complete trail. Calls made from inside programmatic_tool_caller are not recorded, and sub-agents run without a session. OpenTelemetry spans for tool calls and delegation exist but are only exported when configured.

- **default configuration** (default; raw 0.25 → 0.25)
  - **S L1:** The default record is a mutable latest-state snapshot that is overwritten and may be summarized, not a per-call log. — [harness-py/src/strands_harness/agent.py:475](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L475); [strands-py/src/strands/session/snapshot_session_manager.py:225-226](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/session/snapshot_session_manager.py#L225-L226) (verified)
    - *To reach the next level:* A structured record of every tool call kept intact.
  - **C L1:** Main-loop tool calls appear in the snapshot; programmatic_tool_caller inner calls and sub-agent sessions are not recorded. — [harness-py/src/strands_harness/tools/programmatic_tool_caller.py:164](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/programmatic_tool_caller.py#L164); [harness-py/src/strands_harness/agent.py:451](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L451) (verified)
    - *To reach the next level:* All built-in tools, including inner and delegated calls.
  - **D L1:** On by default but stored inside the working directory where the agent's own write and shell tools can change it. — [harness-py/src/strands_harness/defaults.py:35](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L35) (verified)
    - *To reach the next level:* Store the record outside the workspace.
  - **B L1:** Best-effort snapshot saved per message; no fail-closed behaviour. — [harness-py/src/strands_harness/agent.py:475](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L475) (inferred)
    - *To reach the next level:* Errors surfaced and durable per-action records.
- **opt-in OpenTelemetry tracing** (alt; raw 0.35, cap G1 → 0.35) ← counted
  - **S L2:** The SDK emits structured execute_tool spans with arguments and results. — [strands-py/src/strands/telemetry/tracer.py:100](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/telemetry/tracer.py#L100) (verified)
    - *To reach the next level:* Actor attribution (requesting principal, approver).
  - **C L2:** Spans cover the model loop, tool calls and delegation through the shared executor. — [strands-py/src/strands/telemetry/tracer.py:100](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/telemetry/tracer.py#L100); [harness-py/src/strands_harness/tools/programmatic_tool_caller.py:175](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/programmatic_tool_caller.py#L175) (inferred)
    - *To reach the next level:* Approvals/denials and extension calls confirmed recorded.
  - **D L0:** Only exported when OTEL_TRACES_EXPORTER is set. — [harness-py/src/strands_harness/telemetry.py:39-41](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/telemetry.py#L39-L41) (verified)
    - *To reach the next level:* On by default.
  - **B L1:** Exporter is best-effort batching; actions do not depend on records. — [harness-py/src/strands_harness/telemetry.py:39-41](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/telemetry.py#L39-L41) (inferred)
    - *To reach the next level:* Durable per-action flush.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

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

The SDK can cap turns and token spend per invocation, but the harness never sets those limits, so a default run is unbounded. Some bounds are on by default: sub-agent delegation stops at depth 2, web_fetch times out at 30 seconds, programmatic_tool_caller has VM limits and a 15-minute wall clock, and shell commands default to 120 seconds, though the model chooses that timeout per call. Cancellation is cooperative and does not stop a shell command that is already running.

- **S L2:** When set, Limits caps turns, output tokens and total tokens in code, alongside per-tool timeouts. — [strands-py/src/strands/types/agent.py:152-153](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/types/agent.py#L152-L153); [strands-py/src/strands/vended_tools/shell/shell.py:54](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/vended_tools/shell/shell.py#L54); [strands-py/src/strands/agent/agent.py:630](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/agent/agent.py#L630) (verified)
  - *To reach the next level:* No wall-clock limit or side-effect rate limits; halt is cooperative.
- **C L2:** Limits cover the top-level loop and tools have their own timeouts; sub-agents and background tasks don't share the parent budget. — [harness-py/src/strands_harness/tools/subagent.py:515](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/tools/subagent.py#L515); [strands-py/src/strands/background_tasks/_types.py:23](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/background_tasks/_types.py#L23) (verified)
  - *To reach the next level:* Sub-agents and background tasks counted against the same budget.
- **D L1:** No turn or token limit by default; the shell timeout is a model-chosen argument; delegation depth defaults to 2. — searched `rg -n "limits"` in `harness-py/src/strands_harness/agent.py` → 0 hits (the harness never sets per-invocation Limits); [strands-py/src/strands/vended_tools/shell/shell.py:54](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/vended_tools/shell/shell.py#L54); [harness-py/src/strands_harness/defaults.py:33](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/defaults.py#L33) (verified)
  - *To reach the next level:* Sensible loop limits on by default.
- **B L1:** A runaway default run can loop and spend indefinitely; background tasks default to no timeout. — [strands-py/src/strands/background_tasks/_types.py:23](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/strands-py/src/strands/background_tasks/_types.py#L23); [harness-py/src/strands_harness/agent.py:217](https://github.com/strands-agents/harness-sdk/blob/8ba04f70af542e41aadbc2eb8093b1a07df5ec96/harness-py/src/strands_harness/agent.py#L217) (verified)
  - *To reach the next level:* Moderate default ceilings on time and spend.
- **Cap:** G1 — Turn and token budgets exist only as opt-in per-invocation Limits that the harness never sets.

## Rule-of-Two check
[A] untrusted input: web_fetch and file reads, AGENTS.md injection (harness-py/src/strands_harness/plugins/environment.py:108) · [B] sensitive data/systems: ambient AWS chain and full env in shell (strands-py/src/strands/sandbox/stream_process.py:73) · [C] state change / egress: host shell and web_fetch (harness-py/src/strands_harness/agent.py:90) · Same default session? Yes

## Highest-impact improvements
1. Default interventions to the 'ask' preset (or ask for shell/write/edit/web_fetch) so consequential calls need approval. — C2 D L0→L3, +0.150 before caps (Playbook 5)
2. Pass a scrubbed environment (allowlisted variables only) to host subprocesses in NotASandboxLocalEnvironment. — C8 B L0→L2, +0.100 before caps (Playbook 4)
3. Set default per-invocation Limits (turns, total_tokens) in create_harness and cap the shell timeout argument. — C10 D L1→L2, +0.050 before caps (Playbook 3 step 3)
4. Stop auto-loading ./.agent/skills and ./.agent/memory from the working directory without a workspace-trust decision; default memory to a user-scope directory. — C7 D L0→L3, +0.150 before caps (Playbook 2)
5. Block private, loopback and link-local addresses (including after redirects) in web_fetch. — C3 C L1→L2, +0.075 before caps (Playbook 3)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Scored the Python harness defaults; the TypeScript harness, the strands CLI (which has a digest-checked workspace approval for project MCP configs and Cedar permissions in its TUI) and bare SDK Agent() were not scored separately.
- MCP stdio environment handling relies on the MCP Python SDK's default-environment behaviour when env is unset (inferred, not verified in that library).
- The site/ docs tree, test-infra and team/ were not examined beyond searches; no text aimed at AI reviewers was found.
