LLMNotes

第 18 章:代码执行与沙箱

zjc 于 2026-01-18 发布

这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 代码执行能补足模型不擅长的精确计算、数据处理和图表生成,但也带来命令执行、文件访问、网络外连和资源耗尽风险。核心原则是:模型可以提出代码,系统决定在受限环境中运行。

18.1 应用场景

场景 价值
数据分析 精确聚合和统计
报表图表 生成可视化
文件转换 批量处理
代码验证 运行测试
数学计算 避免模型手算
表格清洗 程序化处理

不适合:

  1. 直接执行用户提交的任意系统命令;
  2. 在生产服务器动态执行模型代码;
  3. 让代码访问未脱敏数据库;
  4. 无资源限制地递归计算;
  5. 自动修改生产配置。

18.2 隔离层级

进程内限制
  -> 容器隔离
     -> microVM / sandbox
        -> 独立临时环境
层级 能防什么 局限
Python AST / 权限 危险导入 可被绕过
容器 进程和文件系统隔离 内核漏洞需关注
gVisor / Firecracker 更强内核隔离 成本更高
独立网络 数据外传 需配合代理

不要只靠 Prompt 告诉模型“不要做危险操作”。

18.3 资源限制

Linux 示例:

docker run --rm \
  --memory 512m \
  --cpus 1 \
  --pids-limit 128 \
  --network none \
  --read-only \
  --tmpfs /tmp:size=64m \
  sandbox-python python /work/task.py

还应设置:

  1. CPU 时间;
  2. 真实时间超时;
  3. 输出大小;
  4. 磁盘写入上限;
  5. 进程数;
  6. 环境变量白名单;
  7. 单用户并发数;
  8. 总队列长度。

18.4 文件与数据

Task Volume
  /input/data.csv      只读
  /output/result.json  可写
  /tmp                 临时

规则:

  1. 只挂载任务目录;
  2. 输入脱敏;
  3. 输出扫描病毒和敏感信息;
  4. 限制文件大小和数量;
  5. 产物上传对象存储;
  6. 任务结束后销毁环境。

18.5 网络策略

场景 策略
纯数据分析 network none
需要下载依赖 私有镜像预装
需要访问 API 白名单代理
查询数据库 只读账号和行级权限
用户上传代码 默认禁止外连

允许外连时必须经过审计代理,不能让沙箱直接访问内网任意地址。

18.6 代码生成与执行流程

用户任务
  -> 数据描述
     -> 模型生成代码
        -> 静态检查
           -> 沙箱试运行
              -> 结果校验
                 -> 展示 / 保存

静态检查示例:

import ast

ALLOWED_IMPORTS = {"pandas", "numpy", "json", "math"}

def check_imports(code: str):
    tree = ast.parse(code)
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            names = [alias.name.split(".")[0] for alias in node.names]
        elif isinstance(node, ast.ImportFrom):
            names = [(node.module or "").split(".")[0]]
        else:
            continue
        for name in names:
            if name and name not in ALLOWED_IMPORTS:
                raise ValueError(f"import not allowed: {name}")

静态检查是过滤层,不能替代沙箱。

18.7 结果校验

def validate_result(result: dict, source_rows: int):
    if not isinstance(result, dict):
        raise ValueError("result must be object")
    total = result.get("total_amount")
    if total is not None and total < 0:
        raise ValueError("total amount invalid")
    if source_rows == 0 and result.get("row_count", 0) != 0:
        raise ValueError("row count mismatch")
    return result

还要检查:

  1. 输出 schema;
  2. 数值范围;
  3. 图表是否非空;
  4. 异常是否被吞掉;
  5. 是否读取了额外文件;
  6. 结果与样本手工计算一致。

18.8 依赖管理

推荐预构建镜像:

FROM python:3.12-slim
RUN pip install --no-cache-dir pandas pyarrow matplotlib
RUN useradd -m runner
USER runner
WORKDIR /work

运行时安装依赖会引入供应链风险。确需安装时,应使用私有源、锁版本、缓存和哈希校验。

18.9 审计与观测

日志字段:

{
  "task_id": "exec-001",
  "user_id": "u-1",
  "code_sha256": "...",
  "image": "sandbox-python:v12",
  "cpu_seconds": 2.1,
  "max_memory_mb": 380,
  "network": "disabled",
  "exit_code": 0,
  "duration_ms": 3100
}

保留代码、输入摘要、输出和资源指标,敏感数据按策略脱敏或加密。

18.10 常见攻击

攻击 防护
读取 /etc/passwd 只读 rootfs、最小挂载
反弹 shell 禁网络、限制进程
fork 炸弹 pids limit
大文件打满磁盘 磁盘配额
长循环耗尽 CPU 超时
输出巨量日志 输出截断
访问云 metadata 网络隔离
恶意依赖 预装镜像

本章小结

代码执行必须放在资源受限、文件受限、网络受限的可销毁环境中。模型生成的代码要经过静态检查、沙箱运行和结果校验,执行过程记录资源和审计信息。安全边界由基础设施保证,不依赖模型自律。

思考题

  1. 为什么 Prompt 不能作为代码沙箱的安全边界?
  2. 沙箱应限制哪些资源?
  3. 什么时候允许沙箱访问网络?
  4. 如何校验模型生成代码的结果?
  5. 运行时安装依赖有什么风险?