Skip to main content

沙箱是什么?以 DeepSeek Harness 为例理解三种实现方式

· 9 min read

沙箱不是某项具体技术,而是一类手段的总称:让一段代码在受限范围里运行,触及不到范围之外的东西。操作系统隔离进程、浏览器限制网页脚本、云上租户之间做资源隔离、给 AI Agent 的执行能力加一道边界——沙箱要回答的,都是同一个问题。

  1. 工具层自检:在框架自己的工具函数里拦截;约束的是工具输入,不是整个运行环境
  2. 操作系统隔离:把规则装进内核(Linux 用 bubblewrap + Landlock,macOS 用 sandbox-exec,Windows 用受限令牌 + AppContainer),所有派生进程都受内核监督
  3. 完全隔离:容器 / 虚拟机 / 远程沙箱,与宿主机物理隔离,宿主只暴露有限的受控接口

判断用哪种方式,取决于执行者是谁——约束要放在执行者够不着的地方。DeepSeek Harness 同时采用前两种,在同一个宿主文件系统上落地:策略中心统一管规则,文件工具先解析真实路径再判断,命令执行由 sandbox service 用启动器把规则装进内核。约束可以失败,但不可以被静默绕过


沙箱要解决的是同一个问题

操作系统隔离进程、浏览器限制网页脚本、云上租户之间做资源隔离、给 AI Agent 的执行能力加一道边界——这些场景看着相差很远,沙箱要回答的,都是同一个问题:让一段代码或一个程序在受限范围里运行,无法触及或破坏范围之外的东西。

它不是某项具体技术,而是一类手段的总称——理解这个之后,看任何沙箱方案,焦点就只剩一个:约束放在哪一层、由谁来执行。

三种实现方式:按约束施加的位置分类

按约束施加的位置,沙箱大致分三种,三者在「最终效果」上没有高下,差别只在约束放在哪里、由谁来执行。

工具层自检

Agent 框架通常会给 Agent 提供一组工具——文件读写、网络请求、数据库查询、浏览器操作。Agent 通过调用这些工具操作外部世界。每次调用在真正执行之前,框架可以拦截并判断这个操作在当前策略下是否被允许

  • 文件写入工具:写入前解析目标路径,判断是否落在允许范围内
  • 网络请求工具:检查目标域名是否在允许列表里
  • 数据库工具:限制允许执行的 SQL 类型

这种方式的边界很清楚:它只约束走工具通道的操作。如果框架内另有代码绕开工具直接执行操作,或者 Agent 自己启动一个外部进程来达到目的,这一层检查管不到。它约束的是工具的输入,不是整个运行环境。

操作系统隔离

Agent 有时候不是通过工具来操作,而是直接执行一条命令、启动一个真实的独立外部进程——shell 脚本、编译好的二进制,或者任何能在操作系统上运行的东西。框架既无法控制这个进程内部的每一步行为,也无法在它每次写文件、发网络请求时插一脚

外部进程的不可信是根本性的:它可以派生子进程,可以直接调底层系统调用。任何框架代码里的前置检查,都存在被绕过的路径。所以对这种情况,约束必须放在外部进程够不着的地方——那就是操作系统

要让操作系统去拦框架,得先告诉它规则是什么。这一步需要借助一个外部的「启动器」,把规则翻译成操作系统能执行的限制:

平台启动器
Linuxbubblewrap + Landlock
macOSsandbox-exec
Windows受限令牌(Restricted Tokens)+ AppContainer

启动器把一套规则安装到内核,命令及其所有派生进程都在内核的监督下执行,任何越界操作都会被内核直接拒绝,而不是被代码框架判断

完全隔离

前两种有一个共同前提:Agent 仍然运行在宿主系统上,共享同一个内核和文件系统。约束的对象是「操作」,而不是「环境」。

当约束需要覆盖得足够深——比如不仅限制 Agent 写什么,还要限制它读什么,或者彻底限制它访问任何宿主机资源——就需要把整个环境替换掉。Agent 看到的是一个独立的容器、虚拟机或远程沙箱;它在这个环境里执行的所有操作都只作用于环境内部,与宿主机物理隔离;宿主只暴露有限的受控接口。

代价随之而来:Agent 不再直接操作宿主真实文件,环境的创建和销毁也有开销。三种方式之间的取舍,本质是隔离力度与执行成本的权衡。

关键判断:约束要放在执行者够不着的地方

为什么刚好是这三种分布?因为约束要放在执行者够不着的地方:

方式执行者约束放在哪谁来执行拦截
工具层自检框架代码框架代码里框架自己
操作系统隔离外部进程内核操作系统
完全隔离整个执行体环境本身环境边界

这条线是看任何 Agent 沙箱时最值得抓的视角——选哪种方式,由「执行者是谁」决定。框架代码执行,代码里就能拦截;外部进程执行,只能让内核拦截;整个执行体需要被管,那就换一个环境。

DeepSeek Harness 的具体落地

DeepSeek Harness 同时使用前两种方式,在同一个宿主文件系统上落地——既把破坏范围收紧到工作区,又让 Agent 继续直接改写真实的文件

策略中心:规则统一归口

所有规则统一放在一个策略中心。每次工具调用或命令执行前,策略中心回答两个问题:

  • 当前用的是什么模式
  • 工作区的根目录在哪里

模式有三种:只读模式工作区可写模式完全放开模式。解析优先级是固定的——显式批准的模式高于会话最近一次切换,高于部署时的默认值。工作区根目录取会话创建时的目录;会话切换作为日志里的一条事件被记录,重放日志就能恢复;多会话互不干扰。

文件工具:先解析真实路径

DeepSeek Harness 的文件工具在真正写入前先过一次检查:

  1. 读当前策略
  2. 模式 = 只读 → 直接拒绝
  3. 模式 = 工作区可写 → 重新解析目标路径
    • 把符号链接展平
    • 把上级目录引用(..)全部展平
    • 得到磁盘上的真实位置
  4. 判断它是否落在允许写入的目录集合内

这里有一个不起眼但致命的细节它返回的路径是重新解析过的路径,而不是模型给的原字符串。检查的是谁、写到哪,路径必须是磁盘上实际的位置,不是输入里的字符串。否则攻击者只要构造一个符号链接指向工作区外的位置,就能绕过检查。

命令执行:交给 sandbox service

当 Agent 需要执行一条系统命令时,DeepSeek Harness 的处理流程和文件工具完全不同——它不会直接跑命令,而是把这条命令交给 sandbox service,由后者决定用什么启动器、带什么限制参数来包装它,最后返回一个可以直接 spawn 的完整命令。

包装过程分三步:

  1. 根据当前会话策略,算出哪些路径可写
  2. 把这份可写路径翻译成当前平台启动器能理解的参数——这一步没有模型或外部输入参与,翻译逻辑是写死的,输入是策略,输出是参数列表
  3. sandbox service 把启动器路径、平台参数、原命令拼成一条完整调用

启动器拿到包装后的命令,先把参数按限制装到内核;此后原命令及其所有派生进程的每一次操作都要经过内核、按规则逐条比对,越界即被拒绝。整个过程里原命令感知不到这个限制的存在,也关不掉它

三条工程原则

这套设计里有三条工程原则值得拎出来:

1. 宁可拒绝执行,也不要把裸命令放出去

如果当前机器上没有可用的启动器,框架宁可拒绝执行,也不能把裸的命令放出去。约束可以失败,但不可以被静默地绕过——因为拦截发生在内核一侧,一旦放过,就没有第二次机会。

2. 命令被拒的三种原因要分类

命令被拒之后,要单独判断它是:

  • 被规则拦住了(预期内的拒绝)
  • 命令自己执行失败了(业务异常)
  • 启动器本身出了问题(环境异常)

这三者的处置逻辑完全不同,分类错误会让后续行为偏离预期。

3. 翻译逻辑与外部输入解耦

策略到启动器参数的翻译过程中,没有任何模型或外部输入参与——这一步必须是确定性、可审计的纯函数。任何把模型输出塞进这一步的设计,都等于把规则交给模型自己改写。

一句话总结

Agent 的能力越强,约束它就越不是在限制它,而是在保证它能被安全地使用。DeepSeek Harness 的沙箱设计——工具层解析真实路径、操作系统层用启动器把规则装进内核、策略中心统一归口——回答的都是同一个问题:怎么让 Agent 保留操作能力,同时把破坏半径收窄到工作区

References

  1. 以 DeepSeek Harness 为例,理解沙箱的概念和实现方式 —— LearnLLM_AI, 哔哩哔哩, 2026-09-19