Skip to content

faveos8758/ts-agent-sandbox

Repository files navigation

ts-agent-sandbox

Secure TypeScript/JavaScript sandbox for AI agent code execution

TypeScript License: MIT Node.js

Run untrusted code with V8 isolates (isolated-vm) for JavaScript and TypeScript, optional Docker for stronger isolation, and controlled subprocess execution for Python and shell.


Requirements

Requirement Notes
Node.js 20+ LTS recommended
--no-node-snapshot Required by isolated-vm on Node 20+. This repo sets NODE_OPTIONS in npm scripts; set it yourself if you run node directly.
Python python3 on PATH (or python on Windows) for language: 'python'
Docker Optional: DockerSandbox, executor: 'docker', or USE_DOCKER_EXECUTOR=true

Install

cd ts-agent-sandbox
npm install
npm run build

This writes compiled output to dist/ (ignored by git). Run npm run build after pulling changes before npm start.

The published npm tarball includes dist/, README.md, LICENSE, and .env.example (see the files field in package.json). prepublishOnly runs build before npm publish.

Runtime npm dependencies: express (HTTP API), isolated-vm (JS/TS isolate), typescript (transpile agent TS), dotenv (load .env in src/index.ts only).

Copy .env.example to .env to set PORT, USE_DOCKER_EXECUTOR, etc. The HTTP entrypoint (src/index.ts) loads .env with dotenv before reading process.env. Library-only usage (Sandbox from public-api.ts) does not load .env unless you call dotenv yourself.


Quick start (library)

From another package (after npm install ts-agent-sandbox), import the public API:

import { Sandbox } from 'ts-agent-sandbox';

const sandbox = new Sandbox({
  timeoutMs: 5000,
  memoryLimitMb: 128,
  allowNetwork: false,
  fileSystem: 'none',
});

const result = await sandbox.run(
  `
  function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n-1) + fibonacci(n-2);
  }
  fibonacci(10);
`,
  'javascript'
);

console.log(result.stdout.trim()); // "55"
console.log(result.exitCode); // 0

When developing inside this repo, use path imports (or link the package):

import { Sandbox } from './src/sandbox/Sandbox.js';

TypeScript

User code is transpiled with the TypeScript compiler API (typescript is a runtime dependency). Override compiler options with tsCompilerOptions on Sandbox.

Python

Python runs in a python3 subprocess (timeouts, trimmed env). This is reliable in real paths (including directories with : in the name). Pyodide/WASM is not used by default.

const r = await sandbox.run(
  `
import math
print(math.sqrt(16))
`,
  'python'
);

Bash

Uses /bin/bash -c with a minimal environment and timeout. Weaker than V8 or Docker—treat as convenience only unless you add OS-level isolation.

Virtual filesystem (JS/TS)

fileSystem Behavior
none No require('fs') shim
virtual (default) In-memory fs under /sandbox
tmpdir Real temp directory on disk, exposed as /sandbox/... inside the isolate

HTTP API

npm run dev

Default listen port is 3000 unless PORT is set in the environment or .env (integer 065535; invalid values fall back to 3000).

  • GET /health{ "ok": true }
  • POST /execute — JSON body: { "code": string, "language": string, "options"?: SandboxOptions & { executor?: "docker" } }. If options is present, it must be a plain object (not an array or string).
  • Languages: javascript, typescript, python, bash
curl -s -X POST http://localhost:3000/execute \
  -H "Content-Type: application/json" \
  -d '{"code":"console.log(1+1)","language":"javascript","options":{"fileSystem":"none","timeoutMs":5000,"memoryLimitMb":128}}'

Docker-backed JS/TS (optional)

Requires Docker on the host.

import { DockerSandbox } from 'ts-agent-sandbox';

const dockerSandbox = new DockerSandbox({
  image: 'node:22-alpine',
  timeoutMs: 5000,
  memoryLimitMb: 128,
});

const result = await dockerSandbox.run(`console.log("in container")`, 'javascript');

Or use the default isolate and switch per run:

await sandbox.run(code, 'javascript', { executor: 'docker' });

Environment: If USE_DOCKER_EXECUTOR=true, all JavaScript and TypeScript runs use the Docker executor (no per-request flag needed). Otherwise, pass "executor": "docker" inside the JSON options for /execute, or use DockerSandbox / sandbox.run(..., { executor: 'docker' }) from code.


API

SandboxOptions (constructor and run overrides)

Field Description
timeoutMs Wall-clock limit (default 10000)
memoryLimitMb Isolate or Docker memory hint (default 256)
allowNetwork false, true, or hostname allowlist for injected fetch (JS/TS only)
fileSystem 'none' | 'virtual' | 'tmpdir' (JS/TS)
envVars Extra environment variables (bash / python)
tsCompilerOptions typescript.CompilerOptions for TS transpile
preloadJavaScript Code evaluated in the isolate before user code (JS/TS)

run third argument (optional)

Field Description
Same as SandboxOptions Merged over constructor defaults
executor 'ivm' (default) or 'docker' for JS/TS (docker needs Docker CLI)

RunResult

stdout, stderr, exitCode, durationMs, optional memoryUsedMb (reserved).

Sandbox.runStream

Async generator: buffered stdout / stderr chunks, then an exit chunk (not true byte streaming from the isolate yet).


Scripts

Command Description
npm run dev HTTP server with tsx watch + NODE_OPTIONS=--no-node-snapshot
npm run build Compile to dist/ (invokes the TypeScript compiler)
npm start Run node dist/index.js (build first)
npm test Unit tests (isolated-vm)
npm run test:integration Bash, Python, HTTP API
npm run test:all Full suite (10 tests: unit + integration)

Integration tests expect python3 and /bin/bash on PATH (typical on Linux and macOS; on Windows use WSL or skip integration tests).


Container image

docker build -t ts-agent-sandbox .
docker run --rm -p 3000:3000 ts-agent-sandbox

Or docker compose up using docker-compose.yml.


Project layout

ts-agent-sandbox/
├── src/
│   ├── index.ts              # HTTP server
│   ├── public-api.ts         # npm package entry (library exports)
│   └── sandbox/
│       ├── Sandbox.ts
│       ├── docker.ts         # DockerSandbox
│       ├── types.ts
│       └── executors/
│           ├── IsolatedVmExecutor.ts
│           ├── DockerExecutor.ts
│           ├── BashExecutor.ts
│           └── PythonExecutor.ts
├── tests/
│   ├── unit/
│   └── integration/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── .env.example
├── .gitignore
├── package.json
├── tsconfig.json
├── LICENSE
└── README.md

Security notes

  • isolated-vm greatly reduces what untrusted JS can reach, but no sandbox is perfect. Follow upstream guidance.
  • Python and bash run as the host user with process-level limits only; isolate untrusted workloads with OS containers or VMs if needed.
  • Run Node with --no-node-snapshot when using this package, as required by isolated-vm on Node 20+.

License

MIT — see LICENSE.

About

TypeScript sandbox for running untrusted agent code: V8 isolates (isolated-vm), Docker option, Python/bash subprocesses, Express API

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors