Skip to Content

Sandbox Execution

Run untrusted or AI-generated code safely in isolated Docker containers. The sandbox packages provide tools for creating disposable execution environments with filesystem access, letting workflows execute arbitrary code without risking the host system.

Prerequisites

Docker must be installed and running on the host system before any sandbox tool is invoked. The sandbox tools shell out to the local Docker daemon to create, start, exec into, and remove containers — sandbox_init will fail at runtime if Docker isn’t available.

Any Docker image works — imageName is passed straight through to Docker, so anything you’d put after docker pull (node:18, python:3.11-slim, your own private image) is valid. Images are pulled automatically on first use, so the first init for an unfamiliar image may be slow.

Host ↔ Container Filesystem

sandbox_init takes two related paths:

  • projectOutPath — an absolute path on the host machine.
  • rootPath — a path inside the container (defaults to workspace).

The host directory at projectOutPath is bind-mounted into the container at /<rootPath>. Anything the container writes under /<rootPath> shows up at projectOutPath on the host, and vice versa — it’s the same set of files viewed from two sides. Use this to feed inputs into the sandbox, read outputs back out, and persist artifacts across container destroys.

Setup

import { Module } from '@nestjs/common'; import { SandboxFilesystemModule } from '@loopstack/sandbox-filesystem'; @Module({ imports: [SandboxFilesystemModule], providers: [SandboxWorkflow], exports: [SandboxWorkflow], }) export class SandboxModule {}

Example Workflow

import { z } from 'zod'; import { BaseWorkflow, ToolResult, Transition, Workflow } from '@loopstack/common'; import type { RunContext } from '@loopstack/common'; import { SandboxCreateDirectory, SandboxDelete, SandboxReadFile, SandboxWriteFile, } from '@loopstack/sandbox-filesystem'; import { SandboxDestroy, SandboxInit } from '@loopstack/sandbox-tool'; interface SandboxState { containerId?: string; } @Workflow({ widget: __dirname + '/sandbox.ui.yaml', schema: z.object({ outputDir: z.string().default(process.cwd() + '/out') }), }) export class SandboxWorkflow extends BaseWorkflow<{ outputDir: string }, SandboxState> { constructor( private readonly sandboxInit: SandboxInit, private readonly sandboxDestroy: SandboxDestroy, private readonly sandboxWriteFile: SandboxWriteFile, private readonly sandboxReadFile: SandboxReadFile, private readonly sandboxCreateDirectory: SandboxCreateDirectory, ) { super(); } @Transition({ to: 'sandbox_ready' }) async initSandbox(state: SandboxState, ctx: RunContext): Promise<SandboxState> { const args = ctx.args as { outputDir: string }; const result: ToolResult = await this.sandboxInit.call({ containerId: 'my-sandbox', imageName: 'node:18', containerName: 'sandbox-container', projectOutPath: args.outputDir, rootPath: 'workspace', }); return { ...state, containerId: result.data.containerId }; } @Transition({ from: 'sandbox_ready', to: 'file_written' }) async writeFile(state: SandboxState): Promise<SandboxState> { await this.sandboxCreateDirectory.call({ containerId: state.containerId!, path: '/workspace/src', recursive: true, }); await this.sandboxWriteFile.call({ containerId: state.containerId!, path: '/workspace/src/hello.js', content: "console.log('Hello from sandbox!');", encoding: 'utf8', createParentDirs: true, }); return state; } @Transition({ from: 'file_written', to: 'file_read' }) async readFile(state: SandboxState): Promise<SandboxState> { await this.sandboxReadFile.call({ containerId: state.containerId!, path: '/workspace/src/hello.js', encoding: 'utf8', }); return state; } @Transition({ from: 'file_read', to: 'end' }) async destroySandbox(state: SandboxState): Promise<unknown> { await this.sandboxDestroy.call({ containerId: state.containerId!, removeContainer: true, }); return {}; } }

Available Tools

Container Lifecycle

ToolArgsDescription
sandboxInitcontainerId, imageName, containerName, projectOutPath, rootPathCreate and start container
sandboxDestroycontainerId, removeContainerStop/remove container

Filesystem Operations

ToolArgsDescription
sandboxWriteFilecontainerId, path, content, encoding?, createParentDirs?Write file
sandboxReadFilecontainerId, path, encoding?Read file content
sandboxListDirectorycontainerId, path, recursive?List directory entries
sandboxCreateDirectorycontainerId, path, recursive?Create directory
sandboxDeletecontainerId, path, recursive?, force?Delete file/directory
sandboxExistscontainerId, pathCheck if path exists
sandboxFileInfocontainerId, pathGet file metadata

Command Execution

ToolArgsDescription
sandboxCommandcontainerId, executable, args?, workingDirectory?, envVars?, timeout?Run command in container

Security

  • Path traversal detection and prevention
  • Shell argument escaping
  • Isolated Docker containers with volume mounting
  • Configurable timeouts on command execution

Registry References

  • sandbox-example-workflow — Full sandbox lifecycle: init, create directory, write/read files, list directory, check existence, get file info, delete, and destroy
Last updated on