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
| Tool | Args | Description |
|---|---|---|
sandboxInit | containerId, imageName, containerName, projectOutPath, rootPath | Create and start container |
sandboxDestroy | containerId, removeContainer | Stop/remove container |
Filesystem Operations
| Tool | Args | Description |
|---|---|---|
sandboxWriteFile | containerId, path, content, encoding?, createParentDirs? | Write file |
sandboxReadFile | containerId, path, encoding? | Read file content |
sandboxListDirectory | containerId, path, recursive? | List directory entries |
sandboxCreateDirectory | containerId, path, recursive? | Create directory |
sandboxDelete | containerId, path, recursive?, force? | Delete file/directory |
sandboxExists | containerId, path | Check if path exists |
sandboxFileInfo | containerId, path | Get file metadata |
Command Execution
| Tool | Args | Description |
|---|---|---|
sandboxCommand | containerId, 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