Skip to Content
DocumentationFeaturesSandbox Execution

Sandbox Execution

Run untrusted code in isolated Docker containers using @loopstack/sandbox-tool and @loopstack/sandbox-filesystem.

Setup

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

Example Workflow

import { z } from 'zod'; import { BaseWorkflow, Final, Initial, InjectTool, ToolResult, Transition, Workflow } from '@loopstack/common'; import { SandboxCreateDirectory, SandboxDelete, SandboxReadFile, SandboxWriteFile, } from '@loopstack/sandbox-filesystem'; import { SandboxDestroy, SandboxInit } from '@loopstack/sandbox-tool'; @Workflow({ uiConfig: __dirname + '/sandbox.ui.yaml', schema: z.object({ outputDir: z.string().default(process.cwd() + '/out') }), }) export class SandboxWorkflow extends BaseWorkflow { containerId?: string; @InjectTool() sandboxInit: SandboxInit; @InjectTool() sandboxDestroy: SandboxDestroy; @InjectTool() sandboxWriteFile: SandboxWriteFile; @InjectTool() sandboxReadFile: SandboxReadFile; @InjectTool() sandboxCreateDirectory: SandboxCreateDirectory; @Initial({ to: 'sandbox_ready' }) async initSandbox() { const args = this.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', }); this.containerId = result.data.containerId; } @Transition({ from: 'sandbox_ready', to: 'file_written' }) async writeFile() { await this.sandboxCreateDirectory.call({ containerId: this.containerId!, path: '/workspace/src', recursive: true, }); await this.sandboxWriteFile.call({ containerId: this.containerId!, path: '/workspace/src/hello.js', content: "console.log('Hello from sandbox!');", encoding: 'utf8', createParentDirs: true, }); } @Transition({ from: 'file_written', to: 'file_read' }) async readFile() { const result: ToolResult = await this.sandboxReadFile.call({ containerId: this.containerId!, path: '/workspace/src/hello.js', encoding: 'utf8', }); // result.data.content contains the file content } @Final({ from: 'file_read' }) async destroySandbox() { await this.sandboxDestroy.call({ containerId: this.containerId!, removeContainer: true, }); } }

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