夜雨聆风学习资料网

ARTICLE · 1004641

DeepSeek Harness 源码导读 05|给 Agent 工具前,先画清边界

DeepSeek Harness 源码导读 05|给 Agent 工具前,先画清边界

“运行测试”通常很安全。

但模型可能生成npm test,也可能顺手加上清理目录、安装依赖或访问网络。对人类来说,这些命令风险不同;对操作系统来说,它们都只是准备启动的进程。

DeepSeek Harness 没有把安全压在一个万能开关上,而是分成权限策略、用户审批、工具守卫和进程沙箱几层。要看懂这套设计,先别把它们混着叫“权限”。

先用一张表把四层钉住:

层次
决策对象
典型问题
能不能约束操作系统
权限策略
一类工具调用及其参数
允许、拒绝,还是询问用户
不能
用户审批
当前这一条具体请求
用户是否只允许这一次
不能
工具守卫
已注册工具的最终执行资格
是否存在不可被后续插件撤销的拒绝
不能
进程沙箱
将要启动的子进程
它实际能对哪些文件产生效果
能,范围取决于后端

前面三层发生在 Harness 的工具语义里,最后一层落到宿主平台的进程约束。审批按钮点了“允许”,只代表策略同意继续走,并不等于操作系统已经给命令一张无限通行证。

01权限策略决定怎样处理一类操作

权限预设用于给会话选择一套整体姿态。交互式编码、只读分析和无人值守运行,可以采用不同规则。

当一次工具调用进入tools/pre-execute,策略会结合工具、参数和当前会话作出判断:允许、拒绝,或询问用户。

例如读取工作区文件可以直接允许,写入工作区可能需要确认,危险范围的命令则直接拒绝。

这里的结果仍不是操作系统保证。它是工具执行前的一次策略决定。

策略也不能只靠模型遵守。“不要修改文件”写在提示词里,是给模型看的行为指导;真正的写入限制要由工具守卫或沙箱执行。把提示词当防火墙,和在纸上写“闲人免进”差不多,主要作用是礼貌。

Permission Preset 其实捆绑了两个旋钮

Web 界面里看到的“权限预设”不是新的强制执行层。它把两个彼此独立的状态组合成一个易选的名字:

Sandbox Mode    文件效果范围Approval Policy 遇到 ask 时怎样处理

默认组合中,workspace-write对应workspace-write + askdanger-full-access对应danger-full-access + never。这里的never不是“永远批准”,而是“永远不询问,任何 ask 都确定性拒绝”。

预设切换会把用户选择以及两个旋钮的变化写进会话日志。真正执行时,Shell 读取当前沙箱模式,审批服务读取当前审批策略。若两个旋钮被单独改成不匹配任何具名预设的组合,界面会派生出custom,但custom本身不是可以写入的第三套策略。

这个设计避免把“能写到哪里”和“是否需要人确认”绑死。例如不同部署可以定义自己的只读 + ask、工作区写入 + never 等组合,而底层消费方仍只理解各自的明确状态。

02审批只回答一个具体问题

当策略返回askctx.approval会为这一次操作发起审批。

审批结果只有四种:

allowed-once  只允许当前这一次操作rejected      用户明确拒绝cancelled     请求在等待时被撤回unavailable   没有可用应答者,或应答过程失败

只有allowed-once是放行。

这意味着在无人值守环境里,如果系统无法询问任何人,默认结果是拒绝。它不会因为凌晨三点没人点按钮,就把沉默理解成热烈支持。

会话级审批策略目前有askneverask把问题交给应答者链;never不弹问题,所有询问直接得到rejected。后者适合 CI 等不允许等待人工决定的场景。

每次审批都会获得新的请求 ID,并在会话日志里写下approval/askedapproval/decided。授权与工具调用可以配对审计,但一次允许不会自动升级成永久通行证。

03工具守卫为什么还要再检查

审批之后还有注册表守卫。

守卫采用单调规则:可以拒绝,也可以不表态,但不能撤销已经存在的拒绝。身份字段和冻结参数也不能被中途替换。

这给系统留出了多层叠加空间。平台规则、工作区规则和某个插件自带的限制可以同时工作,后注册的扩展不能轻易把前面的安全决定擦掉。

例如工作流可以临时把某个子任务限制为只读工具集。即使全局会话拥有编辑工具,这个作用域内的守卫仍能拒绝写入。

04沙箱管的是进程的文件效果

命令真正启动前,进程沙箱把原始 argv 包装成平台后端能够约束的 argv。

DeepSeek Harness 定义了三种模式:

read-only          只允许必要的只读文件访问workspace-write    允许写工作区和后端承诺的临时区域danger-full-access 绕过文件隔离

前两种会交给沙箱提供方。danger-full-access则直接运行原始命令,不调用约束后端。

请注意范围:这里约束的是文件系统效果。它不自动等于断网,也不承诺隐藏其他进程。网络与进程可见性不属于这个SandboxMode的定义。

三种模式可以按效果理解:

模式
工作区读取
工作区写入
工作区外写入
是否调用沙箱提供方
read-only
允许
拒绝
拒绝
workspace-write
允许
允许
拒绝,另含后端承诺的临时区
danger-full-access
由宿主权限决定
由宿主权限决定
由宿主权限决定
否,直接运行原始 argv

表里的“拒绝”只描述模式承诺,实际完整度还要看后端返回的fullpartial。此外,它只覆盖进程文件效果;命令是否能联网、能否看到其他进程,要由其他部署措施负责。

这个细节很容易被宣传口号抹平。看到“sandbox”就脑补成全封闭虚拟机,最后往往是安全评审替你补课,而且收费方式通常是加班。

05三个平台怎样实现

本地提供方会按平台选择后端。

Linux 可以使用 bwrap 或 Landlock,macOS 使用 Seatbelt,Windows 使用 ACL 和受限令牌相关机制。

不同后端的能力并不完全相同,所以返回结果会标明fullpartial

full表示后端完整执行了该模式承诺的文件效果限制。partial表示当前后端或内核只能覆盖一部分,例如较旧的 Landlock ABI,或 Windows ACL 在某些边界上的限制。

需要绝对保证的调用方必须检查这个事实,不能把partial当成“差不多 full”。安全语境里,“差不多”通常是事故报告的第一版标题。

如果受限模式下没有可用后端,提供方会失败关闭,也就是报错,而不是悄悄裸跑。系统还会区分两类失败:沙箱本身没能启动,和沙箱正常工作并拦住了命令。两者都会失败,但排查方向完全不同。

沙箱不是在命令字符串外面贴一个标签

消费方把准备执行的 argv 和逐调用策略交给ctx.sandbox.confine()。提供方返回真正应该 spawn 的新 argv,以及当前后端的强制执行完整度和错误识别规则。

例如消费方原本准备运行:

["bash", "-c", "npm test"]

Linux 后端可能把它包装成 bwrap 或 Landlock runner 的 argv;macOS 会使用 Seatbelt profile;Windows 后端会准备 ACL 与受限令牌相关执行路径。Shell 最终启动的是包装后的 argv,而不是先启动原命令再希望它自觉。

运行失败后还要分类:

  • runner failure:约束程序自己没能启动,原命令可能根本没运行。
  • sandbox denial:runner 正常工作,并阻止了越界文件效果。
  • command failure:命令确实运行,但测试失败或返回其他非零状态。

三者在界面上都可能是红色结果,含义却完全不同。只根据退出码判断,会把“沙箱成功拦截”和“沙箱根本没工作”混成一件事。

06为什么审批不能代替沙箱

用户看到的审批问题通常是“是否允许这次命令”。即使用户同意,命令仍可能产生超出预期的文件效果。

沙箱提供第二层约束:允许执行,不等于允许写遍整台机器。

反过来也一样。workspace-write沙箱能限制写入范围,却不判断“删除工作区内所有文件”是否符合用户意图。工作区里的删除操作仍可能需要权限策略和审批。

所以四层各有职责:

权限策略:这类操作应该怎样处理用户审批:这个具体操作是否允许一次工具守卫:还有没有不可撤销的限制进程沙箱:操作系统层面能产生哪些文件效果

少一层不一定立刻出事,但不能假装另外三层会自动补位。

提权重试必须是一项新调用

假设workspace-write下的测试确实需要写入工作区外缓存。合理流程不是在原调用执行到一半时偷偷扩大权限,而是让原调用明确失败或提出升级请求,得到批准后,以显式模式覆盖发起一项新的执行。

新调用会重新拥有自己的调用身份、审批记录、沙箱策略和结果。这样审计日志能回答:哪一次在受限模式下失败,谁批准了哪次提升,提升后的命令产生了什么结果。

如果在同一进程运行中途改规则,系统既难以说明前半段和后半段分别受什么约束,也很难可靠重放。安全状态变化越重要,越需要清楚的调用边界。

07用三个命令做对照

读取package.json,通常只需要权限策略允许和只读文件能力。

运行测试,可能进入受限进程,并根据项目策略直接允许或询问一次。

向工作区外写文件,则可能先被工具策略拒绝;即使获得审批,受限沙箱仍会按照实际模式拦截越界文件效果。

同样是“命令”,路径不同,经过的控制也不同。判断风险要看具体调用,不能只看工具名叫 Bash 就一律放行或一律封死。

把“运行测试并修复”完整走一遍

  1. 模型请求bash("pnpm test"),Agent Loop 先写tool/call
  2. 权限插件在tools/pre-execute检查命令和会话策略,决定直接允许、拒绝或ask
  3. 若需要询问,审批服务写approval/asked,等待当前会话的应答者,再写approval/decided
  4. 只有allowed-once才继续;rejectedcancelledunavailable都让工具主体跳过。
  5. 注册表单调守卫再次检查不可撤销的限制,例如当前子 Agent 是否只允许只读工具。
  6. Shell 消费方解析这一次调用的沙箱策略,把原 argv 交给平台后端包装。
  7. 子进程运行,消费方根据后端方言区分 runner 故障、沙箱拦截和普通测试失败。
  8. 规范结果进入tools/post-execute,随后冻结为tool/result,模型在下一步骤中看到结果。

这八步解释了为什么安全问题不能只问“有没有弹窗”。弹窗只占第三步,前后还有策略、不可撤销限制、平台约束和结果归因。

08给实际使用者的四个建议

第一,首次使用先从只读任务开始,观察工具请求和审批内容。

第二,无人值守运行使用确定性拒绝策略。不要让自动化停在一个永远没人回答的弹窗前,也不要让它默认同意。

第三,只有明确需要时才提升到danger-full-access,并把它当成新的高权限调用,而不是“重试一下”。

第四,检查当前平台的 enforcement 是full还是partial。模式名称和后端实际保证是两件事。

09读源码的顺序

docs/subsystems/permission-presets.zh.mddocs/subsystems/approval.zh.mddocs/subsystems/sandbox.zh.mdpackages/interaction/packages/sandbox/packages/shell/bash-sandbox/packages/shell/pwsh-sandbox/

练习时,可以让 Agent 先运行只读命令,再请求一次写入工作区的操作,观察策略、审批和最终工具结果分别出现在哪里。不要拿重要目录做实验,测试仓库的存在就是为了保护周一早上的心情。

10三句话收住

权限策略、审批、工具守卫和沙箱是四层控制,不是四个名字指向同一个按钮。

审批只授权一次具体操作,无人回答时失败关闭。

沙箱主要约束文件效果,不自动提供完整网络和进程隔离。

下一篇,我们回到整个项目最特别的设计:为什么连 Agent Loop、模型适配器和会话日志都被做成插件,以及 profile 怎样把 247 个包拼成一个可运行产品。

本文基于 DeepSeek Harness 0.1.2-alpha.1。项目仍在开发者预览阶段,运行方式和内部接口可能变化,请以当前官方文档与源码为准。

谢谢你读我的文章。

如果觉得不错,随手点个赞、在看、转发三连吧🙂

如果想第一时间收到推送,也可以给我个星标⭐~

相关学习资料

返回首页浏览学习资料