Skip to content

feat: release strands shell - #1

Merged
mkmeral merged 9 commits into
mainfrom
feat/strands-shell
Jun 15, 2026
Merged

mkmeral merged 9 commits into
mainfrom
feat/strands-shell

Conversation

@mkmeral

@mkmeral mkmeral commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Description

Initial open-source release of Strands Shell — a virtual, Bourne-compatible
shell for AI agents that runs entirely in-process. There is no fork, no exec,
and no direct syscalls; every filesystem, network, and process effect flows
through a single Kernel mediation boundary, giving callers fine-grained control
over what an agent can see and do (files, network domains, credentials) without
containers, microVMs, or firewalls.

One Rust crate is the single source of truth and compiles to multiple targets:

  • a native binary (strands-shell, including the --mcp stdio server) and a Rust library,
  • a Python extension module (strands-shell on PyPI) via PyO3/maturin,
  • a Node.js native addon (@strands-agents/shell on npm) via napi-rs,
  • a wasm32-wasip2 WASM module for any WASI runtime.

What's included

  • Kernel security boundary (src/os.rs) with the bundled VfsKernel
    (src/vfs_kernel.rs) over an in-process VFS (src/vfs.rs); host directories
    are exposed via copy (snapshot) or direct (pass-through) bind mounts.
  • 25 shell builtins (cd, export, set, trap, getopts, read, …) and
    33 isolated coreutils-style commands (cat, grep, sed, sort, jq,
    curl, …), plus an embedded Lua 5.4 interpreter.
  • SSRF-guarded networkingcurl and the kernel's HTTP path block RFC1918,
    link-local, loopback, and IMDS/ECS-task-role addresses at DNS-resolution time
    via SafeResolver; credentials are injected by URL prefix at request time and
    never leak across redirects or to non-allowlisted hosts.
  • MCP support — a built-in MCP server (src/mcp.rs) that exposes the shell
    as tools, and an MCP client (src/mcp_client.rs) that surfaces configured
    [[mcp]] servers as Lua modules.
  • Bindings, docs, and CI — parallel Python/Node binding surfaces, README +
    CONTRIBUTING + AGENTS guides, issue/PR templates, and a CI matrix
    (Rust on Linux/macOS, Python matrix, Node matrix, security audit) plus a
    tag-driven release workflow (PyPI Trusted Publishing, npm, crates.io).

Release positioning

Shipped as v0.1 — an opt-in extra with an explicit no-API-stability
disclaimer. Command coverage is a curated subset rather than full coreutils
parity; the scope statement in the README documents which commands are full
reimplementations vs. subsets.

Related Issues

Documentation PR

N/A — user-facing docs (README, CONTRIBUTING, AGENTS, SECURITY) are included in
this PR.

Type of Change

New feature

Testing

  • I ran the relevant test suites for the bindings I touched (cargo test --workspace --all-targets, pytest tests/python, npm test)
  • If I touched Rust, I ran cargo fmt and cargo clippy

The full suite runs in CI across the Rust (Linux/macOS), Python, and Node
matrices. Integration tests live in tests/ (shell_integration.rs,
curl_integration.rs, lua_integration.rs, mcp_integration.rs, vfs_unit.rs)
alongside tests/python/ and tests/js/.

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Note on "focused and reasonably small": this is the initial release commit, so
it is necessarily large. Subsequent changes will follow the one-logical-change-per-PR
guidance in CONTRIBUTING.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

mkmeral added 4 commits June 13, 2026 16:44
Four independent CI failures on the release commit:

- Rust: cargo doc (-D warnings) failed on a broken intra-doc link in
  vfs_config.rs; ShellBuilder is not in scope there. Qualify the path to
  crate::shell::ShellBuilder::config_file.
- Python: maturin develop aborted with "Couldn't find a virtualenv". Create
  and use a .venv explicitly in the workflow.
- Node 20: the quoted test glob 'tests/js/*.mjs' requires Node's own glob
  support (21+); unquote it so the shell expands it on all versions. Also
  drop the always-failing `npm ci` fallback since package-lock.json is
  gitignored.
- Security audit: npm audit needs a lockfile (ENOLOCK); generate an
  ephemeral one with --package-lock-only before auditing.

Also remove a stray docs/js-bindings.md reference in a JS test name.
A sandbox for adversarial AI agents must never panic on the input it
sandboxes — across the Python/Node FFI a Rust panic aborts the host
process. Six trivially-reachable panics are fixed here:

- ${VAR%?} / #/%% / ## on multibyte text: byte-level glob match offset
  could land mid-codepoint; guard each &str slice with is_char_boundary.
- exit/return/break/continue with an empty-expanding arg (e.g.
  `exit $UNSET`): indexed args[0] on an empty Vec; index defensively.
- uniq -s N: byte-sliced the line; skip by char boundary instead (also
  matches the documented "first N characters").
- mktemp X (template shorter than 6 chars): .max(6) forced a count past
  the template length, underflowing the slice; only default to 6 X's
  when none are present.
- printf '%.Ns': byte-sliced the precision and padded by byte length;
  truncate and pad by character.

Adds regression tests covering each panic trigger.
A single command that mirrors .github/workflows/ci.yml: cargo fmt --check,
clippy (advisory — not a CI merge gate yet), the workspace test suite, cargo
doc with -D warnings, and the binding suites. When their toolchains are set up
it runs Python (maturin develop + pytest) and Node (build + tsc typecheck +
test); otherwise those steps are skipped with a note, so the command is useful
whether or not the bindings are built.

Also adds a TypeScript type-check (`npm run typecheck` → tsc --noEmit) over the
hand-authored public declarations (index.d.ts, native.d.ts) against a usage
test in tests/ts/, and wires it into the Node CI job. Previously nothing
validated the .d.ts surface, so it could silently drift from the JS.

Implemented as a std-only xtask crate (no new Rust deps) plus a
.cargo/config.toml alias, the idiomatic Rust-native task runner. Documents the
commands in CONTRIBUTING.md and CLAUDE.md.
zastrowm
zastrowm previously approved these changes Jun 15, 2026
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md
Comment thread README.md
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
- Security Model: trim the detailed protect-against / best-effort /
  not-protected lists to a short summary and defer the full threat model to
  SECURITY.md (which already carries it). Drops the "escapes from Kernel
  mediation" phrasing a reviewer flagged as confusing.
- MCP Server: condense to a readme-appropriate length and lead the example
  args with `--mcp`.
- release.yml: drop the crates.io publish job (not publishing to crates.io for
  this release) and the unadvertised CLI-binaries job. PyPI + npm only.
- AGENTS.md: stop describing the library as published on crates.io.
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
mkmeral added 2 commits June 15, 2026 13:49
Replaces the bare command list in the README with a per-command reference
documenting, for each of the ~56 builtins and isolated commands, what is
implemented and the notable gaps — missing flags, missing features, and known
correctness divergences from GNU/BSD.

Every entry was validated by running the release binary and diffing against the
system tools. Notable findings captured: regex engine has no backreferences/
lookaround (grep -P unsupported); sed lacks branching and multiline cycle
commands; cut/uniq silently process only the first file and head/tail reject
multiple files; ls -l omits owner/group columns; date +%s isn't expanded;
test/[ and arithmetic treat non-numeric operands as 0; set -o is unsupported;
jq (jaq) throws on missing nested keys. SSRF/credential controls verified
intact and called out as security guarantees, not gaps.

The README "Supported Commands" section now links to COMMANDS.md for detail.
Drop the push-on-every-branch trigger (it double-ran with pull_request and
wasted CI) and scope pull_request to `main` with explicit event types, plus
workflow_dispatch — matching the convention in strands-agents/sdk-python.
Key concurrency on the PR number so superseded runs cancel cleanly.
zastrowm
zastrowm previously approved these changes Jun 15, 2026
WASM is a build target, not a published v0.1 artifact (it's not in the release
or CI workflows), so it doesn't belong in the product README. Removed the
section; the build details already live in CONTRIBUTING.md, which now also
carries the reduced-surface note and states WASM isn't a release artifact.

Collapsed the per-category command list in the README to a short summary plus a
link to COMMANDS.md, which now holds the full command set and a "Shell language"
table for the shell machinery (pipelines, redirections, loops, expansion, ...).

Fixed the now-dangling README#webassembly-wasm anchors in AGENTS.md and
CONTRIBUTING.md.
The release workflow's `napi build` omitted `--js native.js --dts native.d.ts`,
so napi defaulted to writing its generated loader to index.js/index.d.ts —
overwriting the hand-authored customer wrapper (Shell.create, the ShellError
hierarchy). A real npm publish would have shipped the raw napi loader and the
wrong TypeScript types instead of the public API.

Match the flags used by package.json's build script so the generated loader
goes to native.* and the tracked index.* wrapper is left intact.
@mkmeral
mkmeral merged commit b445767 into main Jun 15, 2026
20 checks passed
@zastrowm
zastrowm deleted the feat/strands-shell branch June 17, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants