企业级 Agent 的「手」安全——在操作系统内核级别限制 Agent 的可触达范围。
一、背景:第一篇留下了什么
第一篇介绍了策略引擎 + DLP + 审批的「软防线」:
- 路径 ACL:限制 Agent 只能读写工作区
- 命令黑名单:禁止
rm -rf /等危险命令 - DLP 扫描:阻止敏感数据进入 LLM 上下文
- 审批机制:关键操作需人类确认
但软防线有个本质局限:管不住间接攻击路径。
一个例子
Agent 可以:
write_file写一个 Python 脚本到工作区(合法)exec运行这个脚本(合法)
脚本内部做什么?路径策略和命令正则都拦不住。
二、两种场景,同一个问题
容器沙箱要解决的场景:
场景 A:企业开发环境
企业给开发者配置安全的 AI 开发环境:
- 保护目标:代码资产、测试环境、生产系统
- 管理者:IT 管理员
- 配置方式:企业统一配置安全策略
- 典型用户:企业开发者、CI/CD 环境
场景 B:个人桌面设备
个人用户使用 AI 辅助编程:
- 保护目标:本地文件、个人数据、系统环境
- 管理者:用户本人
- 配置方式:用户在设置中自主选择
- 典型用户:独立开发者、学生、个人用户
共同需求
无论哪种场景,核心需求是一样的:
隔离 Agent 的执行环境,避免它破坏你的系统。
三、解决方案:容器沙箱
容器沙箱解决的是:在操作系统内核级别限制 Agent 的可触达范围。
软防线(策略 + 审批 + DLP):告诉 Agent "你不该做这个"
硬隔离(容器沙箱):让 Agent "做不了这个"
两者不是替代关系,而是纵深防御的两层。
四、核心设计原则
原则 1:灵活的配置方式
企业场景:管理员统一配置,开发者无感知
安全策略 → 强制启用容器隔离 → 开发者无法绕过
个人场景:用户自主选择
设置 → 安全级别 → 用户主动选择透明模式/容器隔离
桌面客户端的用户就坐在电脑前,有能力也有权利决定自己的安全水位。
原则 2:按需获取
沙箱所需的容器镜像不内置于安装包,用户启用时按需下载。
- 安装包不因沙箱功能膨胀
- 不需要沙箱的用户零开销
- 镜像版本可独立于客户端版本更新
原则 3:零强制依赖
容器运行时(Docker / Podman)是可选组件。
- 检测到 → 启用沙箱
- 未检测到 → 引导安装,不拦截,不阻断
- 可以随时切换模式
原则 4:优雅降级
任何环节失败都不应导致功能不可用。
容器执行失败 → 回退到审批模式在宿主机执行
镜像下载失败 → 提示重试,当前使用透明模式
运行时不可用 → 引导安装,当前使用透明模式
五、用户流程
5.1 企业场景(管理员配置)
IT 管理员配置
│
▼
企业策略强制启用"容器隔离"
│
▼
开发者启动 Agent
│
├── 已安装运行时 → 直接使用容器隔离
└── 未安装运行时 → 提示联系 IT 安装
5.2 个人场景(用户自主)
设置 → 安全 → 执行环境
│
├─ ○ 透明模式(默认)
│
└─ ● 容器隔离
│
▼ 环境检查
✓ 检测到 Podman 5.3.0
沙箱镜像:未安装
[下载沙箱镜像] (12 MB)
5.3 未检测到运行时
环境检查
✗ 未检测到容器运行时
容器隔离需要 Docker 或 Podman。
企业环境:联系 IT 管理员安装
个人环境:brew install podman(macOS)
[我已安装,重新检测] [暂不安装,使用透明模式]
六、技术架构
6.1 整体位置
tool_registry.execute(name, params)
│
├── Phase 1a: Policy Gate(策略检查)
├── Phase 1b: Plugin Hooks(审批)
│
├── Phase 2: Around(执行环境)
│ ├── sandbox = "transparent" → 宿主机直接执行
│ └── sandbox = "isolated" → 容器执行器
│
└── Phase 3: After(DLP + 审计)
6.2 容器执行器
核心模块:
ContainerSandbox
├── detect() # 检测容器运行时
├── ensure_image() # 确保沙箱镜像已就绪
├── start() # 启动沙箱容器
├── exec(command) # 在容器内执行命令
├── is_running() # 检查容器状态
└── cleanup() # 销毁容器
6.3 容器生命周期
应用启动 + 启用容器隔离
│
▼
ContainerSandbox.start()
│
├── 检查是否已有运行中的沙箱容器
│ └── 有 → 复用
│
└── 无 → 创建新容器
│
docker run -d \
--name deskclaw-sandbox \
-v $WORKSPACE:/workspace \
--network none \
--cap-drop ALL \
--read-only \
--memory 512m \
--cpus 1 \
--pids-limit 128 \
deskclaw-sandbox:1.0.0 \
sleep infinity
│
▼
容器就绪(持续运行直到应用退出)
容器在整个应用生命周期内保持运行,不是每条命令启一个容器。每次 exec 仅增加 ~50ms 延迟。
七,安全约束
| 约束 | 参数 | 作用 |
|---|---|---|
| 网络隔离 | --network none | 完全断网,Agent 无法通过 curl/wget 泄露数据 |
| 能力丢弃 | --cap-drop ALL | 丢弃所有 Linux capabilities |
| 权限锁定 | --security-opt no-new-privileges | 禁止提权 |
| 只读根 | --read-only | 根文件系统只读,仅 /workspace 和 /tmp 可写 |
| 临时目录 | --tmpfs /tmp:size=64m | 限制 /tmp 大小 |
| 内存限制 | --memory 512m | 防止内存耗尽 |
| CPU 限制 | --cpus 1 | 限制 CPU 使用 |
| 进程限制 | --pids-limit 128 | 防止 fork bomb |
| 工作目录 | -v $WORKSPACE:/workspace | 仅挂载工作区 |
八、沙箱镜像
8.1 镜像内容
基础镜像:busybox(~1.5 MB),包含基础 shell 工具。
如果命令需要 busybox 不包含的工具(如 git、python),沙箱执行将返回 "command not found",此时降级到审批模式在宿主机执行。
8.2 镜像分发
构建侧 用户侧
──────── ────────
Dockerfile │
│ │
▼ ▼
docker build 检测到"镜像未安装"
│ │
▼ ▼
docker save -o sandbox.tar 从对象存储下载 .tar
│ │
▼ ▼
上传到对象存储 (OSS/S3) docker/podman load -i
│
▼
镜像就绪
九、降级策略
三层降级
Level 0: 容器隔离(最高安全)
│ 容器执行失败 / 命令需要宿主机环境
▼
Level 1: 审批模式(现有行为)
│ 用户关闭审批 / 策略设为 transparent
▼
Level 2: 直接执行(最低安全)
降级触发条件
| 触发条件 | 降级行为 | 用户感知 |
|---|---|---|
| 容器运行时未安装 | 退回透明模式 | 提示安装 |
| 沙箱镜像未就绪 | 退回透明模式 | 提示下载 |
| 容器启动失败 | 退回透明模式 | 日志记录 + 通知 |
| 命令在容器内执行失败 | 不降级,返回错误 | Agent 自行处理 |
十、为什么不做内置 Runtime?
这是最常见的问题:「为什么不内置 Docker?」
我们的选择:不内置
| 方案 | 优点 | 缺点 |
|---|---|---|
| 内置 Docker | 功能完整 | 安装包膨胀 ~500MB、需要 daemon、开机自启 |
| 不内置,检测用户环境 | 零膨胀、用户/企业可控 | 需要安装 |
核心思路
- 企业:IT 预装或强制安装,开发者无感知
- 个人:引导安装,但不强制
- 都能用:优雅降级,没有运行时也能工作
十一、与前两篇的关系
| 篇 | 控制对象 | 核心能力 |
|---|---|---|
| 第1篇 | Agent 的「手」(工具执行) | 策略引擎 + DLP + 审批 |
| 第2篇 | Agent 的「脑」(模型访问) | URL 即凭证 + 动态端点 |
| 第3篇 | 执行环境 | 容器沙箱隔离 |
第一篇解决「工具能不能做」,第二篇解决「模型能不能访问」,第三篇解决「在哪里做」。
十二、总结
容器沙箱的核心设计理念:
- 兼顾两种场景——企业统一配置 + 个人自主选择
- 按需获取——不内置镜像,零膨胀
- 优雅降级——任何环节失败都能回退
- 纵深防御——软防线 + 硬隔离配合
无论是保护企业代码资产,还是保护个人电脑数据,核心思路是一样的:
在操作系统内核级别限制 Agent 的可触达范围。
本文是「企业级 Agent 安全方案」系列第 3 篇