Configuring Strands Shell
A fresh shell can reach nothing. You open it up by declaring three things: which directories the agent sees, which credentials get injected on which requests, and which URLs the agent may reach. This guide covers each, plus resource limits and the TOML format that captures the whole policy in one file.
Every option is available both as a constructor argument and as a TOML key. Use the constructor when the policy is dynamic (per session, computed at runtime); use TOML when the policy is static and you want it under version control or shared with the MCP server.
The examples below use the Python API. The Node.js API takes the same options as a config object with camelCase keys (allowedUrls, configFile, maxOutput), and the TOML format is identical across both.
A bind maps a host directory into the shell’s virtual filesystem. The agent sees the destination path; everything outside a bound path doesn’t exist.
Each bind has a mode that decides how host and sandbox relate:
copysnapshots the host directory into the VFS at construction time. The agent’s reads and writes stay inside the sandbox and don’t touch your host files. Use this for source code.directpasses reads and writes through to the host in real time. The agent can modify your live files, and host-side changes after construction are visible to the agent. Use this only for designated output directories.
import strands_shell
shell = strands_shell.Shell( binds=[ strands_shell.Bind("/host/project", "/workspace", mode="copy"), strands_shell.Bind("/tmp/output", "/output", mode="direct"), ],)Add readonly=True to reject writes through a mount even in direct mode, which lets you expose a live host directory for reading without risking modification.
strands_shell.Bind("/host/reference", "/ref", mode="direct", readonly=True)Credentials
Section titled “Credentials”A credential rule attaches a secret to a URL prefix. When a command makes a request to a matching URL, the Kernel injects the secret as a bearer token at request time. The agent doesn’t hold the value: it doesn’t appear in the environment, in command output, or in the Lua scripting context.
Provide the secret one of two ways, and exactly one: an inline token, or an env_var resolved against the process environment when the shell is constructed.
shell = strands_shell.Shell( credentials=[ strands_shell.Cred("https://api.example.com/", env_var="API_TOKEN"), ], allowed_urls=["https://api.example.com/"],)
# The bearer token from $API_TOKEN is injected automatically.result = shell.run("curl https://api.example.com/v1/status")The Kernel matches on URL prefix with a path-boundary check, and it injects only on the original request. It doesn’t re-inject on a redirect, even a redirect back to the same host, which closes the credential-exfiltration path where an agent follows a redirect to a logging endpoint.
Network access
Section titled “Network access”By default curl blocks private address ranges (RFC1918, link-local, loopback, and cloud metadata endpoints such as IMDS) while letting public URLs through. The block happens at DNS-resolution time, so a public hostname that resolves to a private address is still blocked.
To permit a specific internal host, add its URL prefix to allowed_urls.
shell = strands_shell.Shell( allowed_urls=["https://internal-api.corp.example.com/"],)Behavioral settings
Section titled “Behavioral settings”Three top-level settings tune how commands run:
envseeds environment variables into the shell.timeoutsets a per-command wall-clock limit in seconds. It defaults to 30, which bounds runaway commands out of the box; raise it for long-running work. It must be a positive, finite number.umasksets the file-creation mask. The default is0o022.
shell = strands_shell.Shell( env={"PROJECT": "demo"}, timeout=30.0,)Resource limits
Section titled “Resource limits”Resource caps go in a single Limits bundle, separate from behavioral settings so protective caps stay visually distinct from runtime behavior. Override only the caps you care about; the rest keep their defaults.
shell = strands_shell.Shell( limits=strands_shell.Limits( max_output=1 << 20, max_file_size=10 << 20, ),)| Limit | Default | Caps |
|---|---|---|
max_output | 1 MiB | Bytes of output captured from a single command |
max_file_size | 10 MiB | Bytes a single file may reach on write or read |
max_fds | 128 | Open file descriptors at once |
max_bg_jobs | 8 | Concurrent background jobs |
max_pipeline | 16 | Stages in a single pipeline |
max_input | 1 MiB | Bytes of a single input |
max_inodes | 10,000 | Total files and directories in the VFS |
max_depth | 64 | Directory nesting depth |
These caps are best-effort. They stop a runaway agent from exhausting memory or hanging; they aren’t a defense against an adversary actively trying to break out. For hard guarantees, see the security model.
TOML configuration
Section titled “TOML configuration”When the policy is static, declare it in a TOML file. The Python and Node.js constructors accept a config file path, and the MCP server reads the same format through its --config flag. Pass a file and explicit constructor arguments together, and the explicit arguments win.
umask = "022"
[[bind]]mode = "copy"source = "/host/project"destination = "/workspace"
[[bind]]mode = "direct"source = "/tmp/output"destination = "/output"
[[cred]]url = "https://api.openai.com/v1/"methods = ["POST"]kind = "bearer"api_key_env = "OPENAI_API_KEY"
allowed_urls = ["https://api.openai.com/"]
[env]PROJECT = "demo"
[limits]timeout = 30max_output = 1048576Load it in code:
shell = strands_shell.Shell(config_file="sandbox.toml")A few rules the parser enforces:
- Unknown keys are rejected, so a typo like
timeout_secondsfails loudly instead of being silently ignored. - In TOML,
bindmode defaults tocopywhen omitted. Note that this differs from the Python and Node.js constructors, which default todirect. Always specifymodeexplicitly in code. - A
credentry needs akind(bearerorquery) and exactly one ofapi_keyorapi_key_env. Aquerycredential also needs aparamnaming the query parameter. timeoutis in whole seconds and defaults to 30 when the key is absent. A value of0is rejected, because it would expire every command immediately.
Next steps
Section titled “Next steps”- Commands: the full command inventory and supported flags.
- MCP Server: serve this configuration to any MCP-compatible agent.
- Security Model: how binds, credential injection, and the SSRF guard hold up, and where they stop.