Logo
热心市民王先生

关键代码验证

提供可复用的沙箱集成架构与完整代码实现,包括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)
    }
}

下一章:风险评估与结论