Summary article

Strands Shell: the agent sandbox that isn't one

June 17, 2026

A deep look at the new open-source agent shell from the Strands team — its in-memory VFS, credential mediation, SSRF guard, and where it fits in an ecosystem that already has Code Interpreter, E2B, Modal, and Claude Code.

Last night, Clare Liguori (AWS Principal Engineer) tweeted:

“Introducing Strands Shell 🐚 A shell made for agents. Written in Rust, with bindings for Python and Node.js. Control what agents can see and do with files, network domains, and creds in the shell. All in-process, no fork/exec/syscalls, <1ms cold start.”

The repo — strands-agents/shell — had been in private development since February, went public two days ago, and already sits at 68 stars. I spent the evening reading the source, running the test suite (1,757 tests, all green), and mapping it against the things people will immediately compare it to: AgentCore Code Interpreter, E2B, Modal sandboxes, Claude Code’s Agent tool, and CloudFlare Code Mode.

The short version: Strands Shell isn’t a sandbox, and it isn’t a container runtime. It’s a mediation layer — and that’s exactly what makes it interesting.

Community first reactions

The launch-day conversation on X surfaced clear signals about what people want from a tool like this:

No formal roadmap yet — the repo has zero open issues and zero milestones — but the community vectors are clear.


What it actually is

Strands Shell gives your agent a shell that can’t see your real filesystem. You configure what the shell can see at construction time via binds:

from strands_shell import Shell, Bind

shell = Shell(
    binds=[
        Bind("/host/data", "/workspace", mode="copy"),
        Bind("/host/output", "/output", mode="direct", readonly=False),
    ],
    credentials=[CredEntry(url="api.example.com", api_key_env="EXAMPLE_KEY")],
)
result = shell.run("grep ERROR /workspace/logs | sort | uniq -c | sort -rn | head")

The shell runs in-process — no subprocess, no fork/exec, no container daemon. All shell commands are implemented in Rust against an in-memory virtual filesystem. Cold start is under 1ms because there’s nothing to boot.

That last sentence is worth sitting with: this is a shell that doesn’t call /bin/sh. Every command — grep, curl, jq, ls, cat, wc, sort, head — is implemented as a Rust function operating on an in-memory inode tree. The Kernel trait mediates every filesystem operation, every network request, every credential resolution.


The VFS: not Docker, not CoW, smarter than it looks

This is where the technical story gets interesting. The virtual filesystem has two bind modes with very different semantics:

mode: "copy" — one-time snapshot at build time

When you bind with mode="copy" (the default), the shell recursively walks the host directory and copies every file into memory at construction time. The code is remarkably straightforward:

// src/vfs.rs:844-887 — called once, at Shell::build()
fn copy_from_host(vfs: &mut Vfs, src: &Path, dest: &str, uid: Uid, gid: Gid) {
    for each entry in src:
        if directory  mkdir in VFS, recurse
        if symlink   create symlink inode
        if file      std::fs::read() into InodeData::File(Vec<u8>)
                        // executable bit preserved on Unix
}

After construction, the VFS is completely independent of the host. Changes in the shell don’t touch the host. Host changes after construction are invisible. There’s no copy-on-write, no layering, no overlay filesystem — it’s a flat HashMap<Ino, Inode> of bytes. This means:

mode: "direct" — live host passthrough (use carefully)

Direct mode creates HostFile and HostDir inodes that resolve to the real filesystem on every read/write. Symlink traversal is canonicalized to prevent escape. If you set readonly: false (the default for direct), writes go to the host. This is the mode to use when the agent needs to produce persistent output or interact with live data.

The key insight: there is no snapshot mechanism. The README draws a comparison to Docker image filesystems, but it’s aspirational, not implemented. There’s no docker commit equivalent, no layered CoW, no incremental diff. If you want snapshot semantics, you either use copy mode (which is a point-in-time copy) or you build your own serialization layer on top.

The inode types

The VFS supports a richer set of inode types than you’d expect from a tool this young:

Inode type Purpose
File(Vec<u8>) Regular file, content in memory
Dir(BTreeMap) Directory with sorted children
Symlink(String) Symbolic link (target is a string)
CharDevice / BlockDevice / Fifo Special files for /dev/null-style semantics
HostFile(Path, readonly) Passthrough to real file
HostDir(Path, readonly) Passthrough to real directory

The VFS starts with an empty /, creates standard directories (/home/lash, /tmp, /usr/bin, /bin), then processes binds. The agent user is lash — a nice touch that makes the inside of the shell feel like a real Linux environment without exposing one.


The security model: mediation, not sandboxing

The README and SECURITY.md are explicit about this:

“Strands Shell is a mediation layer, not a security sandbox. It does NOT protect against memory-safety exploits in the shell engine itself, timing side-channels, or an attacker who controls the host process.”

This is the right call. The shell mediates three things through its Kernel trait:

1. Filesystem mediation

Every path access goes through Kernel::resolve_path(). The VFS is deny-by-default — if a path isn’t in the inode tree (copy mode) or under a configured bind (direct mode), it returns ENOENT. There’s no /etc/passwd to leak, no /proc to enumerate, no .ssh to exfiltrate.

2. Network mediation (check_url + credential injection)

Before any HTTP request, the kernel calls check_url(url). It blocks:

If the URL passes, resolve_credential(url) checks if a CredEntry matches. Each credential has a kind (currently "bearer"), a set of HTTP methods, a URL pattern, and either an inline api_key or an api_key_env reference. The kernel injects Authorization: Bearer <token> into the request — the agent’s shell code never sees the credential.

This “agent never sees the credential” pattern is an emerging design principle with two distinct architectural approaches. Pipelock (720★, Apache 2.0) takes the opposite placement: an out-of-process network proxy that sits between the agent and the internet. Its design is “capability separation” — the agent has secrets in its environment but no direct network access; Pipelock has network access but can’t access the agent’s secrets. It scans outbound requests for credential leaks (62 DLP patterns, with optional redaction that rewrites matched values to typed placeholders like <pl:aws-access-key:1> before they leave), blocks exfiltration, and injects auth at the boundary. Bonus: kill switch, Ed25519-signed flight recorder, and OS-native process sandbox (Landlock + seccomp on Linux).

Strands Shell and Pipelock aren’t competitors — they compose. Pipelock at the agent boundary for full egress control; Strands Shell inside the execution context for filesystem and command mediation. Same principle, different layer in the stack.

3. Command allowlisting

The shell ships with a curated set of commands (grep, curl, jq, sort, wc, head, cat, ls, find, sed, awk, xargs, and more — full list in COMMANDS.md). There’s no ssh, no scp, no nc, no package manager. You can configure which commands are available, but you can’t add arbitrary binaries — there’s no exec.


Where this fits: the three-axis framework

You may be trying to place this, like I was — is it a sandbox? A container runtime? A competitor to Claude Code’s Agent tool? It’s none of those. Strands Shell, Code Interpreter, Claude Code Agent tool, and CloudFlare Code Mode operate on different axes. Here’s the framework:

Axis 1: Execution model — how does code run?

Model Description Example
Tool-calling Agent calls one tool at a time. Each result enters LLM context. Agent reasons, decides next call. OpenAI function calling, Claude tool use
One-shot code execution Agent writes complete script. Sandbox runs it. Only final output returns to agent context. Code Interpreter, CloudFlare Code Mode, Strands Shell

This is the axis CloudFlare named when they said “Code Mode beats Tool Mode.” The insight: LLMs are better at writing code to call APIs than at calling APIs directly through tool-use tokens they’ve rarely seen in training data. And one-shot execution keeps intermediate results out of context — the agent gets the answer, not the raw 500-line grep output.

Axis 2: Coordination model — how do agents work together?

Model Description Example
Single agent One LLM, one session, one toolset Basic ChatGPT, basic Claude Code
Sub-agent delegation Parent spawns children, collects summaries Claude Code Agent tool, pi.dev /run reviewer, Hermes delegate_task
Workflow orchestration A deterministic script (not an LLM) defines a graph of agents with conditional routing Claude Code dynamic workflows (ultracode)
Agent teams Peers share a task list, claim work, coordinate via messages pi-agent-teams, Claude Code agent teams

Axis 3: Session model — how are sessions managed?

Model Description Example
Turn-based interactive Ongoing conversation loop with memory and iteration Claude Code interactive, E2B PTY sessions
One-shot fire-and-forget Execute and produce output, no state retained Code Interpreter invoke, Strands Shell run()

Where everything lands

Tool Execution model Coordination Session Isolation tech
Strands Shell One-shot shell None (it’s a tool) One-shot In-process mediation
AgentCore Code Interpreter One-shot code None (it’s a tool) One-shot (per-invoke, session-scoped state) Firecracker microVM
CloudFlare Code Mode One-shot (TS in V8) None One-shot V8 isolate
E2B Both (commands + PTY) None Session-based Firecracker microVM
Modal Sandbox Both None Session-based gVisor container
Daytona Both None Session-based Docker (Kata optional)
Claude Code Agent tool Tool-calling Sub-agent delegation Turn-based Host process (agent-configured)
Claude Code dynamic workflows Tool-calling Workflow orchestration Turn-based per sub-agent Host process

Strands Shell isn’t competing with Claude Code’s Agent tool — they’re on different axes. You could use Strands Shell inside a Claude Code sub-agent as its execution tool. Strands Shell isn’t competing with Code Interpreter either — it’s a library you embed; Code Interpreter is a managed service. They solve different parts of the problem.


Comparison: Strands Shell vs. AgentCore Code Interpreter

AgentCore Code Interpreter is the AWS-managed answer to “my agent needs to run code safely.” It’s a Firecracker microVM with 100+ pre-installed Python libraries, three network modes (Sandbox/Public/VPC), IAM role integration, CloudTrail observability, per-second billing, and built-in session lifecycle management.

Here’s the side-by-side:

Dimension Code Interpreter (AWS managed) Strands Shell (OSS library)
Isolation Dedicated Firecracker microVM per session In-process mediation (no VM boundary)
Cold start Seconds (microVM boot + runtime init) <1ms (in-process construction)
Languages Python, JavaScript, TypeScript Bourne shell semantics + Lua scripting
Pre-installed tooling 100+ Python libraries (boto3, numpy, pandas, duckdb, opencv, spacy…) Curated shell commands (grep, curl, jq, awk, sed…)
Filesystem Per-session isolated, S3/EFS mountable In-memory VFS with copy/direct bind modes
Network Sandbox (S3 only), Public, or VPC via ENI URL-level allow/deny with credential injection
IAM / auth Execution role, MMDS-injected temp credentials CredEntry — bearer tokens injected per-domain
Observability CloudTrail spans, CloudWatch metrics (session count, duration, invocations, errors) None built-in (you instrument)
Session lifecycle StartSessionInvokeCodeInterpreterStopSession shell.run(cmd) — atomic, no session state between calls
Deployment AWS-managed, referenced by ARN Embed in your process (pip install / npm install)
Cost Per-session billing Free (you host it)
File size limits Inline up to 100MB, S3 upload up to 5GB Bound by available memory (copy mode loads files into RAM)
Multi-tenancy Hardware isolation between customers No isolation between Shell instances in the same process

The rule of thumb: If your agent runs on AWS and needs Python data analysis with managed security, reach for Code Interpreter. If your agent needs sub-millisecond shell pipelines, runs outside AWS, or you want library-level control over the execution environment, reach for Strands Shell.

They compose well: a Strands agent running on AgentCore Runtime can use Code Interpreter as its Python execution tool and embed Strands Shell for shell-heavy workflows — each handling what it’s best at.


Three use cases where Strands Shell shines

1. Configuration auditing with mediation

An agent auditing Nginx configurations across a fleet doesn’t need access to the whole filesystem. Bind /etc/nginx in copy mode — config files are small, the copy is instant, and the VFS is isolated. The agent runs grep -rn "listen 80" /etc/nginx | wc -l, sorts by server block, and returns a structured report. It never sees /etc/shadow, never touches the real filesystem. For larger or remote data, direct mode with readonly: true lets the agent stream through logs on an attached volume without copying them into memory first.

2. Eval harnesses

Running 10,000 agent evaluations requires sub-millisecond cold starts and perfect reproducibility. Strands Shell constructs in under 1ms, uses exactly the same VFS content every time (copy mode), and leaves no side effects. Compare to Docker-based eval harnesses where container startup alone adds 90ms–2s per test case.

3. MCP server for multi-agent teams

Strands Shell can run as an MCP server over stdio, making it available to any MCP-compatible agent:

{
  "mcpServers": {
    "shell": {
      "command": "uvx",
      "args": ["strands-shell", "--mcp", "--config", "sandbox.toml"]
    }
  }
}

Multiple agents can share a shell server, each getting its own isolated VFS per session config. The MCP tools (shell_spawn, shell_grep, shell_curl, etc.) are exposed with full JSON schema — the agent never touches a raw shell prompt.


When NOT to use it

Strands Shell is explicit about its limits:


The decision matrix

Your situation Use
Agent on AWS, needs Python data analysis, managed security AgentCore Code Interpreter
Agent needs sub-ms shell pipelines, library-level control Strands Shell
Agent needs interactive terminal session with state E2B PTY or Modal Sandbox
Agent needs full OCI container with custom toolchain E2B / Modal / Daytona / Northflank
Agent needs TypeScript execution at the edge, zero cold start CloudFlare Code Mode (Dynamic Workers)
Multiple agents need to coordinate with conditional routing Step Functions (AWS) or Strands Graph (SDK)
You need both shell + Python, AWS-native, fully managed Code Interpreter + embed Strands Shell for shell tasks
You’re building an eval harness with 10k+ runs Strands Shell (sub-ms cold start, zero side effects)
You want to give multiple agents a shared shell tool via MCP Strands Shell MCP server

Strands Shell is one of those tools that’s easy to mis-categorize because it looks like things we already know — a shell, a sandbox, a container runtime. It’s none of those. It’s a filesystem mediation layer with a shell interface, and it occupies a narrow but genuine gap in the agent tooling landscape: the space between “give the agent unrestricted subprocess.run()” and “provision a full microVM for every shell command.” That gap matters more than it first appears.


Strands Shell source code exploration — VFS internals, bind semantics, credential injection, SSRF guard — was done against the strands-agents/shell repo at commit 9bf907c.