Skip to content

About

No description, website, or topics provided.

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Spin Agent Sandbox

Run untrusted JavaScript/TypeScript and Python in isolated WebAssembly components on Spin. This repository includes the sandboxes, an npm bundler, the @spinframework/sandbox client, and examples.

What this repository contains

Why CodeAct

A powerful way to use a model is CodeAct: rather than only calling a fixed set of tools, let its action be code it writes and runs. The model generates a program, runs it, reads the output, and repeats.

CodeAct's hard requirement is an untrusted code sandbox, which is what this repo provides. examples/ai-code-executor is a complete CodeAct agent built on it.

References: ReAct (ICLR 2023) · CodeAct (Apple, 2024; arXiv).

Prerequisites

  • Spin CLI;
  • Rust and the Preview 2 target: rustup target add wasm32-wasip2;
  • Node.js and npm;
  • Python 3.12;
  • Bytecode Alliance wac >= 0.12.0 and wasm-tools >= 1.246.1 on PATH.

Quick start

Build and serve all components:

spin up --build

How it works

An agent or application sends code and a sandboxId to either the JavaScript or Python sandbox. The sandbox runs either a script or HTTP handler, returning captured output or the handler response. Use the SDK or Spin service chaining to keep sandbox endpoints private.

Architecture

Each deployed sandbox composes guard (HTTP entry), guard-io (capability guard), and its language guest. guard-io scopes KV data, captures stdio, filters egress, and stubs unsupported interfaces. Canonical WIT definitions live in wit/; each sandbox's build-composed.sh produces the deployment artifact.

API

Component Route Purpose
ts-sandbox POST /ts/... Run JavaScript or TypeScript
py-sandbox POST /py/... Run Python
bundler (experimental) POST /bundle/... Bundle extra npm dependencies

POST this envelope to /ts/... or /py/...:

{
  "code": "print(6 * 7)",
  "sandboxId": "demo",
  "allowedHosts": ["https://api.example.com"],
  "kvEnabled": true,
  "input": { "method": "GET", "path": "/" }
}

code and sandboxId are required; sandboxId must be non-empty and at most 256 bytes. allowedHosts narrows egress for outbound HTTP; missing or empty denies it. Entries use scheme://host[:port]; *.example.com matches subdomains only and :* allows any port. CIDR and port ranges are not supported, wildcard hosts grant nothing, and malformed entries return 400. kvEnabled is false by default; set it to true to allow access to the scoped key-value store. input creates the request delivered to an HTTP handler.

Code can be either a script or a handler. A script returns captured output:

{ "stdout": "42\n", "stderr": "", "error": null }

For a handler, TypeScript exports default { async fetch(request) {} } and Python defines handle_request(request) -> Response; its response is returned directly. A Python handler can stream its body by returning (status, headers, chunks), where chunks is an iterable of bytes or str; a generator failure truncates the response after its headers.

# Python script
curl -s localhost:3000/py/run -d '{"code":"print(6 * 7)","sandboxId":"demo"}'

# TypeScript handler with input
curl -s localhost:3000/ts/run -d '{
  "code":"export default { async fetch(r) { return new Response(r.url) } }",
  "sandboxId":"demo", "input":{"path":"/hello"}}'

Dependencies Included in the sandbox

TypeScript includes cheerio, node-html-parser, zod, js-yaml, nanoid, ulidx, uuid, lodash, date-fns, dayjs, and @spinframework/spin-kv.

Python includes numpy, tabulate, markdown, jinja2, regex, beautifulsoup4, sqlalchemy, tiktoken (without its downloadable encodings, so get_encoding() fails; supply your own ranks to tiktoken.Encoding), pydantic, pyyaml, and the standard library. The bundler (experimental) packages additional npm dependencies; the component READMEs list the complete language-specific sets.

Bundler (experimental)

The bundler resolves npm packages through the esm.sh project, which allows it to download and use packages without an installation or build step. This is an experimental integration: some packages may not bundle or run. See the compatibility report for the packages tested in the integration suite.

By default, the bundler uses the publicly hosted and rate-limited https://esm.sh server. If you require higher limits or more control, you can also use a self-hosted mirror (see esm.sh HOSTING.md for instructions). For this, point the esm_base_url variable at your own instance, for example via SPIN_VARIABLE_ESM_BASE_URL=https://esm.example.com.

Scoped key-value store

When kvEnabled is true, each sandboxId sees a private view of the shared default store. Keys use an unambiguous length-prefixed namespace, and both runtimes expose the standard Spin KV API:

from spin_sdk import key_value
store = key_value.open_default()
store.set("k", b"v"); store.get("k")
import * as kv from "@spinframework/spin-kv";
const store = kv.openDefault();
store.setJson("k", { v: 1 }); store.getJson("k");

Deployment model

In production, run sandboxes as private components and reach them through Spin service chaining or the @spinframework/sandbox client library. The AI code executor shows the complete wiring.

Sandbox endpoints do not authenticate callers: any component allowed to reach a private route can choose an ID and policy. Grant access only to trusted callers, or authenticate public routes. The application must map requests to sandbox IDs and set allowedHosts and kvEnabled itself; never accept these from model output or unchecked users.

Testing

Run unit tests with:

cargo test --workspace --locked
cd packages/typescript/sandbox && npm test
cd py-sandbox && python3 -m unittest discover -s tests -t .

Run the end-to-end suites, including the security gates, against both sandboxes:

spin up --build --listen 127.0.0.1:3007 &
hurl --variable base_url=http://127.0.0.1:3007 --test tests/smoke.hurl
BASE=http://127.0.0.1:3007 tests/run-examples.sh

See RELEASES.md for CI, the on-demand bundler gates, publishing, and template updates.

Limitations

  • No time or memory limits. The sandbox enforces no per-request CPU, wall-clock, or memory limits for the code it executes, beyond what Spin already enforces. The only built-in caps are on size: code over 5 MB (or an oversized request body) is rejected with 413, and captured stdout/stderr is truncated at 256 KB.
  • Stateless. Each run is single-shot. Only the scoped KV store persists.
  • KV lifetime and quotas. KV has no per-sandbox quota or automatic cleanup. Once an ID is retired, remove its data with Interpreter.javascript(...).kv(id).clear(); coordinate cleanup with writers.
  • Secrets. Code can read any secrets provided in its code, input, or component environment. Do not configure sensitive environment variables on sandbox components; there is no host-side credential injection for outbound requests.
  • Fixed capabilities. No filesystem, shell, or subprocess, and no pip/npm install at runtime. The pre-bundled dependency set (plus, for JS, whatever the bundler can package) is the whole toolbox.
  • Egress DNS. Private IP literals and local/internal names are denied by the component, but deployment networking or Spin must deny allow-listed hostnames that resolve to private addresses.
  • Component sizes. The engines plus bundled deps are large (TS ≈ 30 MB, Python ≈ 85 MB).

About

No description, website, or topics provided.

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages