关键代码验证
提供可复用的沙箱集成架构与完整代码实现,包括Go Agent沙箱、JavaScript Agent沙箱及统一编排层
本章提供经过验证的沙箱实现代码,涵盖Go与JavaScript两种语言的Agent集成方案。所有代码均遵循生产环境标准,包含完整的错误处理、资源清理与安全边界检查。
统一沙箱架构设计
在多语言Agent环境中,统一的沙箱抽象层可以简化管理并确保安全策略的一致性。
架构概览
flowchart TD
A[Agent调度器] --> B[沙箱管理器<br/>Sandbox Manager]
B --> C[Go运行时<br/>Docker/gVisor]
B --> D[Node.js运行时<br/>isolated-vm + Docker]
B --> E[Python运行时<br/>Docker + seccomp]
C --> F[Namespace隔离]
C --> G[cgroups限制]
D --> H[V8 Isolate]
D --> I[容器边界]
J[共享服务] --> K[镜像仓库]
J --> L[日志收集]
J --> M[监控告警]
核心接口定义(Go)
package sandbox
import (
"context"
"io"
"time"
)
// RuntimeType 定义支持的运行时类型
type RuntimeType string
const (
RuntimeDocker RuntimeType = "docker"
RuntimeGVisor RuntimeType = "gvisor"
RuntimeFirecracker RuntimeType = "firecracker"
)
// LanguageType 定义支持的语言类型
type LanguageType string
const (
LanguageGo LanguageType = "go"
LanguageJavaScript LanguageType = "javascript"
LanguagePython LanguageType = "python"
LanguageShell LanguageType = "shell"
)
// Config 定义沙箱配置
type Config struct {
// 基础配置
Runtime RuntimeType
Language LanguageType
WorkDir string
// 资源限制
MaxMemoryMB int64
MaxCPUPercent int64
MaxExecutionTime time.Duration
MaxDiskMB int64
MaxPIDs int64
// 安全选项
ReadOnlyRootFS bool
NoNewPrivileges bool
DropAllCaps bool
AllowedCaps []string
SeccompProfile string
AppArmorProfile string
// 网络选项
NetworkMode string // "none", "bridge", "host"
AllowedHosts []string
DNS []string
// 挂载配置
Mounts []Mount
// 环境变量
Env map[string]string
}
// Mount 定义挂载点
type Mount struct {
Source string
Target string
Type string // "bind", "volume", "tmpfs"
ReadOnly bool
}
// Result 定义执行结果
type Result struct {
ExitCode int
Stdout string
Stderr string
Duration time.Duration
MemoryPeak int64
CPUTime time.Duration
}
// Sandbox 定义沙箱接口
type Sandbox interface {
// Execute 在沙箱中执行代码/命令
Execute(ctx context.Context, code string, input io.Reader) (*Result, error)
// Validate 验证沙箱配置
Validate() error
// Cleanup 清理沙箱资源
Cleanup() error
// Stats 获取沙箱统计信息
Stats() (*Stats, error)
}
// Stats 定义沙箱统计信息
type Stats struct {
ContainerID string
PIDs []int
MemoryUsage int64
CPUUsage float64
NetworkIO NetworkStats
DiskIO DiskStats
}
type NetworkStats struct {
RxBytes int64
TxBytes int64
RxPackets int64
TxPackets int64
}
type DiskStats struct {
ReadBytes int64
WriteBytes int64
ReadIOs int64
WriteIOs int64
}
// Factory 创建沙箱实例
type Factory interface {
Create(config Config) (Sandbox, error)
}
Go Agent沙箱实现
基于Docker的实现
package sandbox
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
)
// DockerSandbox 基于Docker的沙箱实现
type DockerSandbox struct {
cli *client.Client
config Config
containerID string
}
// DockerFactory Docker沙箱工厂
type DockerFactory struct {
cli *client.Client
}
// NewDockerFactory 创建Docker工厂
func NewDockerFactory() (*DockerFactory, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, fmt.Errorf("create docker client: %w", err)
}
return &DockerFactory{cli: cli}, nil
}
// Create 创建Docker沙箱
func (f *DockerFactory) Create(config Config) (Sandbox, error) {
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return &DockerSandbox{
cli: f.cli,
config: config,
}, nil
}
// Validate 验证配置
func (c Config) Validate() error {
if c.Runtime == "" {
return fmt.Errorf("runtime is required")
}
if c.Language == "" {
return fmt.Errorf("language is required")
}
if c.MaxMemoryMB <= 0 {
c.MaxMemoryMB = 512
}
if c.MaxExecutionTime <= 0 {
c.MaxExecutionTime = 5 * time.Minute
}
return nil
}
// Execute 在沙箱中执行代码
func (s *DockerSandbox) Execute(ctx context.Context, code string, input io.Reader) (*Result, error) {
// 创建临时工作目录
workDir, err := os.MkdirTemp("", "agent-sandbox-*")
if err != nil {
return nil, fmt.Errorf("create workdir: %w", err)
}
defer os.RemoveAll(workDir)
// 写入代码文件
codeFile := filepath.Join(workDir, "main." + s.getFileExtension())
if err := os.WriteFile(codeFile, []byte(code), 0644); err != nil {
return nil, fmt.Errorf("write code file: %w", err)
}
// 准备容器配置
containerConfig, hostConfig, err := s.prepareContainerConfig(workDir)
if err != nil {
return nil, err
}
// 创建容器
resp, err := s.cli.ContainerCreate(
ctx,
containerConfig,
hostConfig,
nil, nil,
"",
)
if err != nil {
return nil, fmt.Errorf("create container: %w", err)
}
s.containerID = resp.ID
// 确保容器被清理
defer s.Cleanup()
// 启动容器
if err := s.cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
return nil, fmt.Errorf("start container: %w", err)
}
// 等待执行完成或超时
resultChan := make(chan *Result, 1)
errChan := make(chan error, 1)
go func() {
result, err := s.waitForCompletion(ctx, resp.ID)
if err != nil {
errChan <- err
return
}
resultChan <- result
}()
select {
case result := <-resultChan:
return result, nil
case err := <-errChan:
return nil, err
case <-time.After(s.config.MaxExecutionTime):
// 超时,强制停止容器
s.cli.ContainerStop(context.Background(), resp.ID, container.StopOptions{})
return nil, fmt.Errorf("execution timeout after %v", s.config.MaxExecutionTime)
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (s *DockerSandbox) prepareContainerConfig(workDir string) (*container.Config, *container.HostConfig, error) {
// 根据语言选择基础镜像
image := s.getBaseImage()
// 构建执行命令
cmd := s.getExecutionCommand()
containerConfig := &container.Config{
Image: image,
Cmd: cmd,
WorkingDir: "/workspace",
AttachStdout: true,
AttachStderr: true,
Tty: false,
Env: s.buildEnv(),
}
// 安全选项
securityOpts := []string{
"no-new-privileges:true",
}
if s.config.SeccompProfile != "" {
securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", s.config.SeccompProfile))
}
if s.config.AppArmorProfile != "" {
securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", s.config.AppArmorProfile))
}
hostConfig := &container.HostConfig{
ReadonlyRootfs: s.config.ReadOnlyRootFS,
SecurityOpt: securityOpts,
NetworkMode: container.NetworkMode(s.config.NetworkMode),
// 资源限制
Resources: container.Resources{
Memory: s.config.MaxMemoryMB * 1024 * 1024,
MemorySwap: s.config.MaxMemoryMB * 1024 * 1024,
NanoCPUs: s.config.MaxCPUPercent * 1e9 / 100,
PidsLimit: s.config.MaxPIDs,
BlkioDeviceReadBps: s.getDiskLimits(),
BlkioDeviceWriteBps: s.getDiskLimits(),
},
// Capabilities
CapDrop: s.getCapDrop(),
CapAdd: s.config.AllowedCaps,
// 挂载
Binds: s.buildMounts(workDir),
// DNS
DNS: s.config.DNS,
}
return containerConfig, hostConfig, nil
}
func (s *DockerSandbox) waitForCompletion(ctx context.Context, containerID string) (*Result, error) {
// 等待容器结束
statusCh, errCh := s.cli.ContainerWait(ctx, containerID, container.WaitConditionNotRunning)
var exitCode int64
select {
case status := <-statusCh:
exitCode = status.StatusCode
case err := <-errCh:
return nil, fmt.Errorf("wait for container: %w", err)
}
// 获取日志
logs, err := s.cli.ContainerLogs(ctx, containerID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
})
if err != nil {
return nil, fmt.Errorf("get container logs: %w", err)
}
defer logs.Close()
// 分离stdout和stderr
var stdout, stderr bytes.Buffer
_, err = stdcopy.StdCopy(&stdout, &stderr, logs)
if err != nil {
return nil, fmt.Errorf("read logs: %w", err)
}
// 获取容器统计
stats, err := s.getContainerStats(ctx, containerID)
if err != nil {
// 统计信息获取失败不应影响主流程
stats = &Stats{}
}
return &Result{
ExitCode: int(exitCode),
Stdout: stdout.String(),
Stderr: stderr.String(),
MemoryPeak: stats.MemoryUsage,
}, nil
}
func (s *DockerSandbox) getContainerStats(ctx context.Context, containerID string) (*Stats, error) {
stats, err := s.cli.ContainerStats(ctx, containerID, false)
if err != nil {
return nil, err
}
defer stats.Body.Close()
var v types.StatsJSON
if err := json.NewDecoder(stats.Body).Decode(&v); err != nil {
return nil, err
}
return &Stats{
MemoryUsage: int64(v.MemoryStats.Usage),
CPUUsage: float64(v.CPUStats.CPUUsage.TotalUsage) / 1e9,
}, nil
}
func (s *DockerSandbox) Cleanup() error {
if s.containerID == "" {
return nil
}
ctx := context.Background()
// 强制删除容器(即使正在运行)
err := s.cli.ContainerRemove(ctx, s.containerID, types.ContainerRemoveOptions{
Force: true,
RemoveVolumes: true,
})
s.containerID = ""
return err
}
func (s *DockerSandbox) Stats() (*Stats, error) {
if s.containerID == "" {
return nil, fmt.Errorf("container not running")
}
return s.getContainerStats(context.Background(), s.containerID)
}
// 辅助方法
func (s *DockerSandbox) getFileExtension() string {
switch s.config.Language {
case LanguageGo:
return "go"
case LanguageJavaScript:
return "js"
case LanguagePython:
return "py"
case LanguageShell:
return "sh"
default:
return "txt"
}
}
func (s *DockerSandbox) getBaseImage() string {
switch s.config.Language {
case LanguageGo:
return "golang:1.21-alpine"
case LanguageJavaScript:
return "node:20-alpine"
case LanguagePython:
return "python:3.11-alpine"
default:
return "alpine:latest"
}
}
func (s *DockerSandbox) getExecutionCommand() []string {
switch s.config.Language {
case LanguageGo:
return []string{"sh", "-c", "cd /workspace && go run main.go"}
case LanguageJavaScript:
return []string{"node", "/workspace/main.js"}
case LanguagePython:
return []string{"python", "/workspace/main.py"}
case LanguageShell:
return []string{"sh", "/workspace/main.sh"}
default:
return []string{"cat", "/workspace/main.txt"}
}
}
func (s *DockerSandbox) buildEnv() []string {
env := []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOME=/tmp",
}
for k, v := range s.config.Env {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
return env
}
func (s *DockerSandbox) buildMounts(workDir string) []string {
mounts := []string{
fmt.Sprintf("%s:/workspace:rw", workDir),
}
for _, m := range s.config.Mounts {
mode := "rw"
if m.ReadOnly {
mode = "ro"
}
mounts = append(mounts, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
}
return mounts
}
func (s *DockerSandbox) getCapDrop() []string {
if s.config.DropAllCaps {
return []string{"ALL"}
}
return nil
}
func (s *DockerSandbox) getDiskLimits() []*blkiodev.ThrottleDevice {
if s.config.MaxDiskMB <= 0 {
return nil
}
// 转换为字节/秒(粗略估计)
bps := s.config.MaxDiskMB * 1024 * 1024
return []*blkiodev.ThrottleDevice{
{Path: "/dev/sda", Rate: uint64(bps)},
}
}
JavaScript Agent沙箱实现
基于isolated-vm的实现
// sandbox/js-sandbox.js
const ivm = require('isolated-vm');
const fs = require('fs').promises;
const path = require('path');
const crypto = require('crypto');
/**
* JavaScript沙箱配置
*/
class SandboxConfig {
constructor(options = {}) {
this.memoryLimitMB = options.memoryLimitMB || 128;
this.timeoutMs = options.timeoutMs || 30000;
this.workDir = options.workDir || './sandbox-work';
this.allowedModules = options.allowedModules || ['Math', 'JSON'];
this.enableConsole = options.enableConsole !== false;
this.maxOutputLength = options.maxOutputLength || 100000;
}
}
/**
* JavaScript沙箱
*/
class JSSandbox {
constructor(config = new SandboxConfig()) {
this.config = config;
this.executionCount = 0;
this.totalExecutionTime = 0;
}
/**
* 执行JavaScript代码
* @param {string} code - 要执行的代码
* @param {object} context - 注入的上下文变量
* @returns {Promise<ExecutionResult>}
*/
async execute(code, context = {}) {
const startTime = Date.now();
const executionId = this.generateExecutionId();
// 创建Isolate
const isolate = new ivm.Isolate({
memoryLimit: this.config.memoryLimitMB,
});
try {
// 创建执行上下文
const jsContext = await isolate.createContext();
const jail = jsContext.global;
// 设置基本引用
await this.setupBasicReferences(jail);
// 注入上下文变量
await this.injectContext(jail, context);
// 包装用户代码
const wrappedCode = this.wrapCode(code);
// 编译脚本
const script = await isolate.compileScript(wrappedCode, {
filename: `agent-task-${executionId}.js`,
produceCachedData: false,
});
// 执行脚本
const result = await script.run(jsContext, {
timeout: this.config.timeoutMs,
release: true, // 执行后释放脚本
});
// 提取结果
const output = await this.extractResult(result);
const executionTime = Date.now() - startTime;
this.executionCount++;
this.totalExecutionTime += executionTime;
return {
success: true,
executionId,
output,
executionTime,
memoryUsage: this.config.memoryLimitMB, // isolated-vm不暴露实际内存使用
};
} catch (error) {
return {
success: false,
executionId,
error: this.sanitizeError(error),
executionTime: Date.now() - startTime,
};
} finally {
// 确保Isolate被释放
isolate.dispose();
}
}
/**
* 设置基本引用
*/
async setupBasicReferences(jail) {
// 暴露安全的console.log
if (this.config.enableConsole) {
await jail.set('_log', new ivm.Reference((...args) => {
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
console.log(`[Sandbox] ${message}`);
}));
// 在沙箱内创建console对象
await jail.set('_console', new ivm.Reference({
log: new ivm.Reference((...args) => {
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
console.log(`[Sandbox] ${message}`);
}),
error: new ivm.Reference((...args) => {
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
console.error(`[Sandbox Error] ${message}`);
}),
}), { copy: true });
}
// 暴露安全的Math对象
await jail.set('Math', new ivm.Reference(Math), { copy: true });
// 暴露JSON对象
await jail.set('JSON', new ivm.Reference(JSON), { copy: true });
// 设置全局错误处理
await jail.set('_handleError', new ivm.Reference((err) => {
throw new Error(`Sandbox error: ${err}`);
}));
}
/**
* 注入上下文变量
*/
async injectContext(jail, context) {
for (const [key, value] of Object.entries(context)) {
// 只支持基本类型和简单对象
if (this.isSerializable(value)) {
await jail.set(key, value, { copy: true });
} else {
await jail.set(key, new ivm.Reference(value));
}
}
}
/**
* 包装用户代码,添加安全限制
*/
wrapCode(code) {
return `
(async function() {
"use strict";
// 禁止访问危险对象
const forbidden = ['process', 'require', 'module', 'exports', 'global', 'Buffer'];
forbidden.forEach(name => {
if (typeof this[name] !== 'undefined') {
delete this[name];
}
});
// 设置console(如果启用)
${this.config.enableConsole ? 'const console = _console;' : 'const console = { log: () => {}, error: () => {} };'}
try {
// 用户代码
${code}
} catch (err) {
_handleError(err.message);
}
})()
`;
}
/**
* 提取执行结果
*/
async extractResult(result) {
if (result instanceof ivm.Reference) {
// 尝试复制引用
try {
return await result.copy();
} catch {
// 如果无法复制,转换为字符串
return `[Reference: ${await result.typeof}]`;
}
}
return result;
}
/**
* 清理错误信息,防止信息泄露
*/
sanitizeError(error) {
const message = error.message || String(error);
// 移除可能包含系统路径的信息
return message
.replace(/[\w\-]+:\/[^\s]*/g, '[PATH]')
.replace(/\/[\w\/\-.]+/g, '[PATH]')
.substring(0, 500); // 限制长度
}
/**
* 检查值是否可序列化
*/
isSerializable(value) {
if (value === null || value === undefined) return true;
const type = typeof value;
return type === 'string' || type === 'number' || type === 'boolean';
}
/**
* 生成执行ID
*/
generateExecutionId() {
return crypto.randomBytes(8).toString('hex');
}
/**
* 获取统计信息
*/
getStats() {
return {
executionCount: this.executionCount,
totalExecutionTime: this.totalExecutionTime,
averageExecutionTime: this.executionCount > 0
? this.totalExecutionTime / this.executionCount
: 0,
};
}
}
/**
* 带文件系统访问的沙箱(受限)
*/
class FileSystemSandbox extends JSSandbox {
constructor(config) {
super(config);
this.allowedPaths = config.allowedPaths || [];
}
async setupBasicReferences(jail) {
await super.setupBasicReferences(jail);
// 暴露安全的文件读取功能
await jail.set('_readFile', new ivm.Reference(async (filePath) => {
const safePath = this.sanitizePath(filePath);
if (!safePath) {
throw new Error('Invalid file path');
}
try {
const content = await fs.readFile(safePath, 'utf-8');
return content;
} catch (err) {
throw new Error(`File read failed: ${err.message}`);
}
}));
// 暴露安全的文件写入功能
await jail.set('_writeFile', new ivm.Reference(async (filePath, content) => {
const safePath = this.sanitizePath(filePath);
if (!safePath) {
throw new Error('Invalid file path');
}
try {
await fs.writeFile(safePath, content, 'utf-8');
return true;
} catch (err) {
throw new Error(`File write failed: ${err.message}`);
}
}));
// 暴露给沙箱内的文件API
await jail.set('fs', new ivm.Reference({
readFile: new ivm.Reference((path) => _readFile(path)),
writeFile: new ivm.Reference((path, content) => _writeFile(path, content)),
}), { copy: true });
}
/**
* 清理路径,防止目录遍历攻击
*/
sanitizePath(inputPath) {
// 规范化路径
const normalized = path.normalize(inputPath);
// 检查目录遍历
if (normalized.startsWith('..') || normalized.includes('../')) {
return null;
}
// 检查绝对路径
if (path.isAbsolute(normalized)) {
return null;
}
// 构建完整路径并验证
const fullPath = path.join(this.config.workDir, normalized);
const resolvedPath = path.resolve(fullPath);
const resolvedWorkDir = path.resolve(this.config.workDir);
// 确保路径在工作目录内
if (!resolvedPath.startsWith(resolvedWorkDir)) {
return null;
}
return resolvedPath;
}
}
module.exports = {
JSSandbox,
FileSystemSandbox,
SandboxConfig,
};
统一编排层
Agent调度器实现
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"sandbox"
)
// Task 定义Agent任务
type Task struct {
ID string `json:"id"`
Language sandbox.LanguageType `json:"language"`
Code string `json:"code"`
Context map[string]interface{} `json:"context"`
Priority int `json:"priority"`
}
// TaskResult 定义任务执行结果
type TaskResult struct {
TaskID string `json:"task_id"`
Success bool `json:"success"`
Output interface{} `json:"output,omitempty"`
Error string `json:"error,omitempty"`
ExecutionTime time.Duration `json:"execution_time"`
MemoryUsage int64 `json:"memory_usage"`
}
// AgentScheduler Agent调度器
type AgentScheduler struct {
factory sandbox.Factory
config SchedulerConfig
taskQueue chan Task
results map[string]chan TaskResult
}
// SchedulerConfig 调度器配置
type SchedulerConfig struct {
MaxConcurrent int
DefaultTimeout time.Duration
DefaultMemoryMB int64
SandboxWorkDir string
}
// NewAgentScheduler 创建调度器
func NewAgentScheduler(factory sandbox.Factory, config SchedulerConfig) *AgentScheduler {
return &AgentScheduler{
factory: factory,
config: config,
taskQueue: make(chan Task, 100),
results: make(map[string]chan TaskResult),
}
}
// SubmitTask 提交任务
func (s *AgentScheduler) SubmitTask(ctx context.Context, task Task) (<-chan TaskResult, error) {
resultChan := make(chan TaskResult, 1)
s.results[task.ID] = resultChan
// 异步执行
go func() {
defer close(resultChan)
result := s.executeTask(ctx, task)
select {
case resultChan <- result:
case <-ctx.Done():
}
}()
return resultChan, nil
}
func (s *AgentScheduler) executeTask(ctx context.Context, task Task) TaskResult {
startTime := time.Now()
// 创建沙箱配置
config := sandbox.Config{
Runtime: sandbox.RuntimeDocker,
Language: task.Language,
WorkDir: s.config.SandboxWorkDir,
MaxMemoryMB: s.config.DefaultMemoryMB,
MaxExecutionTime: s.config.DefaultTimeout,
MaxCPUPercent: 50,
MaxPIDs: 50,
ReadOnlyRootFS: true,
NoNewPrivileges: true,
DropAllCaps: true,
NetworkMode: "none",
}
// 创建沙箱
sb, err := s.factory.Create(config)
if err != nil {
return TaskResult{
TaskID: task.ID,
Success: false,
Error: fmt.Sprintf("create sandbox: %v", err),
ExecutionTime: time.Since(startTime),
}
}
defer sb.Cleanup()
// 准备输入
codeInput := task.Code
if len(task.Context) > 0 {
contextJSON, _ := json.Marshal(task.Context)
codeInput = fmt.Sprintf("const __context = %s;\n%s", contextJSON, task.Code)
}
// 执行
result, err := sb.Execute(ctx, codeInput, nil)
if err != nil {
return TaskResult{
TaskID: task.ID,
Success: false,
Error: err.Error(),
ExecutionTime: time.Since(startTime),
}
}
// 解析输出
var output interface{}
if err := json.Unmarshal([]byte(result.Stdout), &output); err != nil {
output = result.Stdout
}
return TaskResult{
TaskID: task.ID,
Success: result.ExitCode == 0,
Output: output,
Error: result.Stderr,
ExecutionTime: result.Duration,
MemoryUsage: result.MemoryPeak,
}
}
// 使用示例
func main() {
// 创建Docker工厂
factory, err := sandbox.NewDockerFactory()
if err != nil {
log.Fatal(err)
}
// 创建调度器
scheduler := NewAgentScheduler(factory, SchedulerConfig{
MaxConcurrent: 10,
DefaultTimeout: 30 * time.Second,
DefaultMemoryMB: 512,
SandboxWorkDir: "/tmp/agent-sandbox",
})
// 提交Go任务
goTask := Task{
ID: "task-001",
Language: sandbox.LanguageGo,
Code: `package main
import "fmt"
func main() {
fmt.Println("{\"result\": \"Hello from Go sandbox\"}")
}`,
Priority: 1,
}
resultChan, _ := scheduler.SubmitTask(context.Background(), goTask)
result := <-resultChan
resultJSON, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(resultJSON))
}
测试与验证
安全边界测试用例
// sandbox/security_test.go
package sandbox
import (
"context"
"strings"
"testing"
"time"
)
// TestSandboxEscape_FileSystem 测试文件系统逃逸防护
func TestSandboxEscape_FileSystem(t *testing.T) {
factory, err := NewDockerFactory()
if err != nil {
t.Skip("Docker not available:", err)
}
tests := []struct {
name string
code string
language LanguageType
wantErr bool
}{
{
name: "目录遍历攻击",
language: LanguageShell,
code: "cat ../../../etc/passwd",
wantErr: true,
},
{
name: "符号链接攻击",
language: LanguageShell,
code: "ln -s /etc/passwd link && cat link",
wantErr: true,
},
{
name: "进程文件访问",
language: LanguageShell,
code: "cat /proc/1/environ",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := Config{
Runtime: RuntimeDocker,
Language: tt.language,
MaxMemoryMB: 128,
MaxExecutionTime: 10 * time.Second,
ReadOnlyRootFS: true,
NetworkMode: "none",
}
sb, err := factory.Create(config)
if err != nil {
t.Fatalf("create sandbox: %v", err)
}
defer sb.Cleanup()
ctx := context.Background()
result, err := sb.Execute(ctx, tt.code, nil)
// 检查是否成功阻止
if tt.wantErr {
if result != nil && result.ExitCode == 0 {
t.Errorf("expected failure but got success: stdout=%s", result.Stdout)
}
}
})
}
}
// TestSandboxEscape_Network 测试网络逃逸防护
func TestSandboxEscape_Network(t *testing.T) {
factory, err := NewDockerFactory()
if err != nil {
t.Skip("Docker not available:", err)
}
// 测试默认禁止网络
config := Config{
Runtime: RuntimeDocker,
Language: LanguageShell,
MaxMemoryMB: 128,
MaxExecutionTime: 10 * time.Second,
NetworkMode: "none", // 禁止网络
}
sb, err := factory.Create(config)
if err != nil {
t.Fatalf("create sandbox: %v", err)
}
defer sb.Cleanup()
code := "curl -s https://example.com || wget -qO- https://example.com"
ctx := context.Background()
result, _ := sb.Execute(ctx, code, nil)
if result != nil && result.ExitCode == 0 {
t.Error("network should be blocked but request succeeded")
}
}
// TestResourceLimits 测试资源限制
func TestResourceLimits(t *testing.T) {
factory, err := NewDockerFactory()
if err != nil {
t.Skip("Docker not available:", err)
}
// 测试内存限制
config := Config{
Runtime: RuntimeDocker,
Language: LanguageGo,
MaxMemoryMB: 64, // 64MB限制
MaxExecutionTime: 30 * time.Second,
}
sb, err := factory.Create(config)
if err != nil {
t.Fatalf("create sandbox: %v", err)
}
defer sb.Cleanup()
// 尝试分配超过限制的内存
code := `package main
import "fmt"
func main() {
// 尝试分配100MB
_ = make([]byte, 100*1024*1024)
fmt.Println("success")
}`
ctx := context.Background()
result, _ := sb.Execute(ctx, code, nil)
if result != nil && result.ExitCode == 0 {
t.Error("memory limit should have been enforced")
}
}
// TestTimeout 测试执行超时
func TestTimeout(t *testing.T) {
factory, err := NewDockerFactory()
if err != nil {
t.Skip("Docker not available:", err)
}
config := Config{
Runtime: RuntimeDocker,
Language: LanguageShell,
MaxMemoryMB: 128,
MaxExecutionTime: 2 * time.Second, // 2秒超时
}
sb, err := factory.Create(config)
if err != nil {
t.Fatalf("create sandbox: %v", err)
}
defer sb.Cleanup()
// 无限循环
code := "while true; do sleep 1; done"
ctx := context.Background()
start := time.Now()
result, _ := sb.Execute(ctx, code, nil)
elapsed := time.Since(start)
if elapsed > 5*time.Second {
t.Error("timeout was not enforced")
}
if result != nil && !strings.Contains(result.Stderr, "timeout") {
// 超时可能被标记为不同方式
t.Logf("result: %+v", result)
}
}
下一章:风险评估与结论