这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 代码执行能补足模型不擅长的精确计算、数据处理和图表生成,但也带来命令执行、文件访问、网络外连和资源耗尽风险。核心原则是:模型可以提出代码,系统决定在受限环境中运行。
18.1 应用场景
| 场景 | 价值 |
|---|---|
| 数据分析 | 精确聚合和统计 |
| 报表图表 | 生成可视化 |
| 文件转换 | 批量处理 |
| 代码验证 | 运行测试 |
| 数学计算 | 避免模型手算 |
| 表格清洗 | 程序化处理 |
不适合:
- 直接执行用户提交的任意系统命令;
- 在生产服务器动态执行模型代码;
- 让代码访问未脱敏数据库;
- 无资源限制地递归计算;
- 自动修改生产配置。
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
还应设置:
- CPU 时间;
- 真实时间超时;
- 输出大小;
- 磁盘写入上限;
- 进程数;
- 环境变量白名单;
- 单用户并发数;
- 总队列长度。
18.4 文件与数据
Task Volume
/input/data.csv 只读
/output/result.json 可写
/tmp 临时
规则:
- 只挂载任务目录;
- 输入脱敏;
- 输出扫描病毒和敏感信息;
- 限制文件大小和数量;
- 产物上传对象存储;
- 任务结束后销毁环境。
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
还要检查:
- 输出 schema;
- 数值范围;
- 图表是否非空;
- 异常是否被吞掉;
- 是否读取了额外文件;
- 结果与样本手工计算一致。
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 | 网络隔离 |
| 恶意依赖 | 预装镜像 |
本章小结
代码执行必须放在资源受限、文件受限、网络受限的可销毁环境中。模型生成的代码要经过静态检查、沙箱运行和结果校验,执行过程记录资源和审计信息。安全边界由基础设施保证,不依赖模型自律。
思考题
- 为什么 Prompt 不能作为代码沙箱的安全边界?
- 沙箱应限制哪些资源?
- 什么时候允许沙箱访问网络?
- 如何校验模型生成代码的结果?
- 运行时安装依赖有什么风险?