Skip to Content

Sandbox Execution

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

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 { LoopstackContext } 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: LoopstackContext): 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