Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions site/src/config/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ sidebar:
- docs/user-guide/evals-sdk/how-to/result_caching
- docs/user-guide/evals-sdk/how-to/experiment_management
- docs/user-guide/evals-sdk/how-to/serialization
- label: Strands Shell SDK
items:
- docs/user-guide/shell-sdk
- docs/user-guide/shell-sdk/quickstart
- docs/user-guide/shell-sdk/configuration
- docs/user-guide/shell-sdk/commands
- docs/user-guide/shell-sdk/mcp-server
- docs/user-guide/shell-sdk/security
- docs/user-guide/versioning-and-support

- label: Examples
Expand Down
133 changes: 133 additions & 0 deletions site/src/content/docs/user-guide/shell-sdk/commands.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: Strands Shell Commands
description: The full command inventory in Strands Shell, with supported flags, shell builtins, and known divergences from GNU and BSD coreutils behavior.
sidebar:
label: "Commands"
---

Strands Shell reimplements a curated subset of POSIX and coreutils in Rust, targeting the operations agents reach for most rather than a complete toolset. This page is the reference for what is supported and where behavior diverges from GNU or BSD: missing flags, unsupported features, and known gaps.

The shell is Bourne-compatible, with pipes, loops, functions, and subshells. The commands group into text processing, file contents, file management, path and system utilities, networking, JSON, search, and scripting.

## General callouts

These notes apply across commands:

- Regex uses the Rust `regex` crate. Backreferences and lookaround are unavailable, so `grep -P` is unsupported and GNU BRE escapes are not translated.
- `jq` is backed by [`jaq`](https://github.com/01mf02/jaq), a jq subset.
- Unsupported flags are rejected, not ignored. Idioms such as `cp -p`, `set -o pipefail`, or `ln -sf` produce an error rather than silently doing the wrong thing.
- Multiple file arguments are not uniformly supported. `cut` and `uniq` read only the first file; `head` and `tail` return a hard error; `cat`, `sort`, and `wc` handle multiple files correctly.
- Malformed numeric input passes silently. `test`, `[`, and arithmetic `$(( ))` treat non-numeric or empty operands as `0` without an error.
- Stdin under `strands-shell -c` is not connected to commands (`bad fd 0`). Use an in-shell pipe instead.

## Text processing

| Command | Notable gaps |
|---|---|
| `grep` | No `-P`, backreferences, lookaround, or `-f`. `-o` on empty-matching patterns emits blank lines. |
| `sed` | No branching (`b`, `t`, `:label`) or multiline (`N`, `D`, `P`); no `-f`. `s///N` replaces the wrong match; range `c` emits per line. |
| `tr` | Missing `[:punct:]`, `[:cntrl:]`, and `[c*n]` repeats. `-c` two-set translate uses the wrong replacement character. |
| `cut` | No `-b` or `--complement`. Reads only the first file when given several. |
| `sort` | No `-c`, `-o`, `-V`, or `-h` (`-h` prints help instead). Keyed-tie order is non-deterministic. Loads all input into memory. |
| `uniq` | No `-w` or `-D`. Reads only the first file when given several. |
| `wc` | No `-m` (char count) or `-L`. Counts are correct; column padding differs from coreutils. |

## File contents

| Command | Notable gaps |
|---|---|
| `cat` | No `-b`, `-s`, `-A`, `-e`, or `-t`; no `-` stdin operand. |
| `head` | No `-c`, negative `-n`, or `head -5` shorthand. Multiple files produce a hard error (no `==>` headers). |
| `tail` | No `-c`, `-f` or `-F` follow, or shorthand. Multiple files produce a hard error. |
| `tee` | No `-i`. Standard usage (stdout plus files, `-a`) is supported. |

## File management

| Command | Notable gaps |
|---|---|
| `cp` | `-r` works, including nested directories; no `-p`, `-a`, `-f`, or `-i`. `cp f f` silently succeeds. |
| `mv` | Rename, move, and cross-mount operations work; no `-f`, `-i`, `-n`, or `-v`. |
| `rm` | `-r`, `-f`, and symlink non-follow behave correctly; no `-d` or `-i`. |
| `mkdir` | No `-m`. An invalid-option path returns exit 0. |
| `rmdir` | No `-p`. |
| `touch` | File creation and timestamp update only; no `-t`, `-d`, `-c`, or `-r` (cannot set a specific time). |
| `ln` | `-s` only; hard links are refused by design; no `-f`, so `ln -sf` fails. |
| `chmod` | Full support. Octal and symbolic modes; no `-R`. |

## Path and system

| Command | Notable gaps |
|---|---|
| `ls` | `-l` omits owner, group, and link count; `-a` omits `.` and `..`. No `-t`, `-S`, `-d`, `-F`, or `-h`. Output is always single-column. |
| `basename` | No `-a` or `-s`. `basename /` and `""` diverge from coreutils. |
| `dirname` | Single operand only. A trailing slash is mishandled (`/usr/lib/` becomes `/usr/lib`). |
| `readlink` | Plain read only; no `-f`, `-e`, or `-m` canonicalization. |
| `mktemp` | No `-u` or `-t`. |
| `date` | `%s` is not expanded; unknown specifiers pass through literally; no `-d` or `-r`. UTC only. |
| `env` | Print-only. `env VAR=v cmd`, `-i`, and `-u` are unsupported; use the shell-prefix form instead. |
| `echo` | Expands escapes by default; `-e` and `-E` are printed literally (escape expansion cannot be disabled). |
| `pwd` | Full support, including `-L` and `-P`. |
| `sleep` | No unit suffixes (`0.1s`). Respects the shell timeout. |
| `true`, `false` | Full support. |

## Networking

| Command | Notable gaps |
|---|---|
| `curl` | GET, POST, JSON, headers, auth, redirects, and `-w` are supported. No `--max-time`, `--connect-timeout`, or `--retry` (risk of an indefinite hang); no `-I`, `-F`, `-A`, `-G`, or cookie jar. SSRF and credential controls are enforced. |

The SSRF guard and credential injection on `curl` are security features, covered in the [security model](security.md). They are not optional and cannot be disabled per command.

## JSON

| Command | Notable gaps |
|---|---|
| `jq` (jaq) | Broad filter coverage. A missing nested key throws an error, which breaks the `.a.b // default` pattern; no `--arg`, `--argjson`, or `-S`; `inputs` and `setpath` are absent. |

## Search

| Command | Notable gaps |
|---|---|
| `find` | `-name`, `-type`, `-maxdepth`, `-exec`, and `-print0` are supported. No `-delete`, `-size`, `-mtime`, `-prune`, or `-regex`. Children are sorted alphabetically, not in readdir order. |
| `xargs` | Space-separated `-n`, `-I`, `-0`, and `-d` are supported; attached forms (`-n1`, `-I{}`) and `-t`, `-r`, `-L`, `-P` are not. Implicitly behaves as `-r`. |

## Scripting

| Command | Notable gaps |
|---|---|
| `lua` | Full support. Sandboxed Lua 5.4 with VFS-backed `io` and `os`. Metatables are disabled (`setmetatable` removed); no `os.date` or `debug`. No wall-clock timeout under a bare `-c`. |

Reach for `lua` when shell syntax gets awkward: multi-step transforms, structured data manipulation, or logic that would otherwise sprawl across a chain of pipes.

## Shell builtins

| Builtin | Notable gaps |
|---|---|
| `test`, `[` | No `[[ ]]`. Non-numeric or empty operands are treated as `0` (succeeds rather than erroring). |
| `printf` | No floating-point (`%f`, `%e`, `%g` print literally); `%d` does not parse `0x` or octal prefixes. |
| `read` | No `-a`, `-n`, or `-d`; a prefix `IFS=` is ignored; `-r` is effectively always on. |
| `set` | No `-o`, so `set -o pipefail` fails; only `e`, `u`, and `x` are supported. |
| `trap` | `EXIT` works; numeric signals (`trap ... 0`) are ignored; `-p` and `-l` are no-ops. |
| `alias` | Can be defined and listed, but aliases are never expanded during execution. |
| `readonly` | Enforced, but a violation returns exit 0; no `-p`. |
| `local` | Works within functions; silently succeeds when used outside a function. |
| `type` | No `-t` or `-a`. |
| `cd` | No `CDPATH`; `-P` and `-L` are not parsed. |
| `getopts`, `export`, `unset`, `shift`, `umask`, `hash`, `wait`, `:` | Full support. |

Arithmetic `$(( ))` handles precedence, bitwise operators, the ternary, hex, and octal correctly. Malformed input returns a wrong result with exit 0 (`$((2 3))` yields `2`, `$((1/0))` yields `0`). Post-increment `x++` and `x--` return the value without updating the variable.

## Shell language

| Feature | Supported |
|---|---|
| Pipelines and lists | `\|`, `&&`, `\|\|`, `;` |
| Redirections | `>`, `>>`, `<`, `2>`, `&>`, here-docs `<<` |
| Conditionals | `if`, `elif`, `else`, `case` |
| Loops | `for`, `while`, `until` |
| Functions | definitions, `local` variables, `return` |
| Grouping | command groups `{ }`, subshells `( )` |
| Expansion | variables (`${VAR:-default}`, `${VAR%pat}`, `${#VAR}`), command substitution (`` `cmd` ``, `$(cmd)`), arithmetic `$(( ))`, globs |
| Quoting | single, double |
| Jobs | background `&` |
| Scripts | `. script.sh`, `source` |
171 changes: 171 additions & 0 deletions site/src/content/docs/user-guide/shell-sdk/configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
title: Configuring Strands Shell
description: Bind host directories, inject credentials per URL, set the network allowlist, and tune resource limits for Strands Shell, in code or from a TOML file.
sidebar:
label: "Configuration"
---

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](mcp-server.md).

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.

## Binds

A bind maps a host directory into the shell's virtual filesystem. The agent sees the destination path; everything outside a bound path does not exist.

Each bind has a mode that decides how host and sandbox relate:

- `copy` snapshots the host directory into the VFS at construction time. The agent's reads and writes stay inside the sandbox and never touch your host files. Use this for source code.
- `direct` passes 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The docs consistently frame copy as the safe default (security.mdx: "the safe choice is the default at every turn"; configuration.mdx line 163: "bind mode defaults to copy when omitted, which is the safe choice"). That is accurate for the TOML parser (default_mode() returns BindMode::Copy), but the programmatic constructors default to direct:

  • Python Bind dataclass: mode: Literal["direct", "copy"] = "direct"
  • Node BindConfig: mode?: 'direct' | 'copy' documented as 'direct' passthrough (default)

So a reader who writes strands_shell.Bind("/host", "/dest") and omits mode= gets a live host-passthrough mount, the exact behavior the :::caution below warns against. Every example here passes mode explicitly, which hides the asymmetry.

Suggestion: Call out that the constructor default is direct (so omitting mode is not the safe choice in code, even though it is in TOML), or note the inconsistency for upstream to reconcile. A one-line note in the Binds section would prevent a reader from assuming the in-code default matches the TOML default.


```python
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.

```python
strands_shell.Bind("/host/reference", "/ref", mode="direct", readonly=True)
```

:::caution
A `direct` bind is live. The agent can read and modify host files in real time, including deleting them. Never `direct`-bind a directory that holds secrets, credentials, or configuration you do not want the agent to change. When in doubt, use `copy`.
:::

## 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 never holds the value: it does not 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.

```python
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 never re-injects 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

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`.

```python
shell = strands_shell.Shell(
allowed_urls=["https://internal-api.corp.example.com/"],
)
```

:::caution
Do not allowlist a bare scheme like `https://`. A prefix that broad permits every host and disables the SSRF guard entirely. List the specific endpoints the agent needs.
:::

## Behavioral settings

Three top-level settings tune how commands run:

- `env` seeds environment variables into the shell.
- `timeout` sets 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.
- `umask` sets the file-creation mask. The default is `0o022`.

```python
shell = strands_shell.Shell(
env={"PROJECT": "demo"},
timeout=30.0,
)
```

## 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.

```python
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 are not a defense against an adversary actively trying to break out. For hard guarantees, see the [security model](security.md).

## 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](mcp-server.md) reads the same format through its `--config` flag. Pass a file and explicit constructor arguments together, and the explicit arguments win.

```toml
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 = 30
max_output = 1048576
```

Load it in code:

```python
shell = strands_shell.Shell(config_file="sandbox.toml")
```

A few rules the parser enforces:

- Unknown keys are rejected, so a typo like `timeout_seconds` fails loudly instead of being silently ignored.
- `bind` mode defaults to `copy` when omitted, which is the safe choice.
- A `cred` entry needs a `kind` (`bearer` or `query`) and exactly one of `api_key` or `api_key_env`. A `query` credential also needs a `param` naming the query parameter.
- `timeout` is in whole seconds and defaults to 30 when the key is absent. A value of `0` is rejected, because it would expire every command immediately.

## Next steps

- [Commands](commands.md): the full command inventory and supported flags.
- [MCP Server](mcp-server.md): serve this configuration to any MCP-compatible agent.
- [Security Model](security.md): how binds, credential injection, and the SSRF guard hold up, and where they stop.
Loading