DeepSeek Harness 沙箱与审批

2026-09-08 00:00    #AI   #工具   #DeepSeek  

沙箱管"命令跑在什么边界里",审批管"这个具体操作是否被允许",两者都默认失败关闭。

ctx.sandbox:按策略包装 argv

模型:消费方交出确切 argv(不是 shell 字符串),后端按文件效果策略包装它。shell 形态消费方要传 ['bash', '-c', command]

ctx.sandbox.confine(argv, policy) 返回 ConfinedArgv = 替换后的 argv + 后端强制执行事实。

SandboxMode 只管控文件系统效果,不含网络与进程可见性

模式行为
read-only只允许必需的数据接收端(如 /dev/null),拒绝写入
workspace-write还允许在工作区根目录及后端承诺的临时区域下写入
danger-full-access绕过隔离,消费方直接 spawn 原始 argv,不调用 ctx.sandbox
安全性质:受限执行必然到达 ctx.sandbox,静默的无隔离透传永远不合法。只有前两种模式会发给提供方,danger-full-access 根本不进沙箱。

强制执行程度与回退规则

后端报告 enforcement: 'full' | 'partial'partial 情形包括:较旧的 Landlock ABI、Windows ACL runner 的 Everyone 与硬链接边界。要求绝对边界的消费方必须把 partial 当"不够"处理。

策略解析回退顺序(三层):

优先级来源说明
最高已批准的显式模式一次性提权重试时传入的 mode,胜过会话策略
其次会话最后一次 sandbox/mode 事件随会话日志持久化,可回放重建
回退部署默认模式无 agent 的调用与没有 cwd 的会话使用配置的根目录

故障关闭:无可用后端时 ctx.sandbox.confineSandboxUnavailableError,错误码 SANDBOX_UNAVAILABLE

动手示例:受限模式下跑一次 bash

 1// 文件路径:my-plugins/sandbox-demo/src/index.ts
 2import type { Context } from '@deepseek-ai/cordis'
 3import { defineTool } from '@deepseek-ai/dsh-tools'
 4
 5export const name = 'sandbox-demo'
 6export const inject = ['tools', 'sandbox', 'sandboxPolicy']
 7
 8export function apply(ctx: Context) {
 9  ctx.tools.register(defineTool({
10    name: 'sandboxed_echo',
11    description: 'Run echo inside the sandbox. The runoob demo command.',
12    parameters: {
13      text: { type: 'string', required: true, description: 'Text to echo' },
14    },
15    output: { schema: { type: 'string' } },
16    async execute(args, exec) {
17      const policy = ctx.sandboxPolicy.resolve({ session: exec.agent?.session })
18
19      const argv = ['bash', '-c', `echo ${JSON.stringify(args.text)}`]
20
21      if (policy.mode === 'danger-full-access') {
22        const { spawn } = await import('node:child_process')
23        // spawn(argv) 并收集输出
24        return `echo ${args.text}`
25      }
26
27      const confined = ctx.sandbox.confine(argv, { mode: policy.mode, workspaceRoot: policy.workspaceRoot })
28
29      if (confined.enforcement === 'partial' && policy.mode !== 'read-only') {
30        return { isError: true, error: { message: 'partial enforcement is not acceptable for runoob demo' } }
31      }
32      return `confined echo ${args.text} (enforcement: ${confined.enforcement})`
33    },
34  }))
35}

生产级消费方是 dsh-bash-sandbox(负责 spawn 与结果归因)。结果分类要区分"沙箱 runner 失败"与"沙箱正常工作但拒绝":两种正交 stderr 分类器 denialSignatures(识别受限命令被阻止)与 runnerFailureRules(识别 runner 执行前拒绝/失败)。消费方应先把 runner 失败作为沙箱基础设施故障上报,而非普通任务失败。

ctx.approval:一次性权限决策

通过 approval/request waterfall 分发问题给应答者;第一个应答者占据唯一决策槽位。结果类型 ApprovalOutcome 是闭合的:

结果含义调用方行为
allowed-once一次性放行唯一放行结果,只授权所询问的那一个操作
rejected明确拒绝拒绝
cancelled请求被撤回(如 signal 中止)拒绝
unavailable无应答者、应答者抛异常或返回值不合规失败关闭,一律拒绝
关键安全性质:缺失、不负责该请求、抛异常或不合规的应答者都产生 unavailable 而不是放行。

按会话审批策略:ask 与 never

策略行为典型场景
ask(默认)委托给应答者链;无应答者时回退为 unavailable交互式 UI,需要人做决定
never确定性返回 rejected,不分发任何应答者CI、无人值守的严格无头姿态

生效值是会话日志中最后一条 approval/policy 事件,回退到服务配置;setApprovalPolicy(session, policy) 是唯一写入路径。never 在服务内部、waterfall 分发前强制执行,prepend 注册的应答者也无法绕过。

审计与模型可见性区别:approval/askedapproval/decided 只写日志,不进入模型 transcript;模型可见的是调用方派生的工具结果与当前运行时上下文快照。

权限预设:两个 knob 捆绑成具名预设

预设沙箱模式审批策略含义
workspace-writeworkspace-writeask可写工作区,敏感操作问用户
danger-full-accessdanger-full-accessnever绕过沙箱、不询问,只能跑在可丢弃环境
事故复盘 0002 提醒:组合时的文件系统访问无法安全地跟随运行时的 bash-only 预设。生产环境里 danger-full-access 只应配给一次性、可丢弃的沙箱环境。

自测

  1. danger-full-access 是否调用 ctx.sandbox?→ 不调用,直接 spawn 原始 argv
  2. 审批链无应答者会怎样?→ unavailable,失败关闭、调用方拒绝
  3. CI 里"绝不询问、确定性拒绝"用什么?→ approval/policy: never