沙箱与权限审批
本教程共 32 篇 · 第 16 篇 · 更新于 2026-08-15 · 约 6 分钟阅读
本节目标:搞懂 dsh 怎么约束 Agent 的危险动作——沙箱圈定进程能碰什么文件,审批决定某一次操作能不能做,以及两个机制如何组合成权限预设。
两道闸门,一个问题
Agent 想执行 rm -rf、想删一个文件、想往外发数据,凭什么拦?dsh 给了两道闸门:
- 沙箱(sandbox):管「命令跑在什么边界里」。它把子进程的 argv 包装起来,按策略限制文件系统效果。
- 审批(approval):管「这个具体操作是否被允许」。它把问题交给人类或机器应答者,做一次性决策。
一句话:沙箱管边界,审批管授权。两者都默认失败关闭(fail closed)——拿不准就当拒绝处理。
沙箱机制
沙箱的服务是 ctx.sandbox。消费方交出确切的 argv(程序加参数,不是 shell 字符串),沙箱按策略返回替换后的 argv,消费方再 spawn 它。比如 bash 工具会传 ['bash', '-c', command] 这样的形状。
沙箱模式(SandboxMode)只管控文件系统效果,不含网络与进程可见性,共三档:
| 模式 | 行为 |
|---|---|
read-only | 拒绝写入,只允许必需的接收端(如 /dev/null) |
workspace-write | 允许在工作区根目录及后端承诺的临时区域下写入 |
danger-full-access | 绕过隔离,消费方直接 spawn 原始 argv,根本不进沙箱 |
注意 danger-full-access 不经过 ctx.sandbox。这保证了一个安全性质:受限执行必然到达沙箱,静默的无隔离透传永远不合法。
策略按每次调用解析:普通工具调用从调用会话的不可变 cwd 派生工作区边界;已批准的提权重试可以显式传更宽的模式。没有可用后端时,confine 抛出 SANDBOX_UNAVAILABLE。
本地提供方按平台选执行器:Linux 用 bwrap/Landlock,macOS 用 Seatbelt,Windows 用 ACL 受限令牌。后端还会报告强制执行完整度:full(管控了该模式承诺的所有文件效果)或 partial(只管控子集——比如较旧的 Landlock ABI 和 Windows ACL 的 Everyone/硬链接边界)。要求绝对保证的消费方必须把 partial 当「不够」处理。
Note沙箱还带回两种 stderr 分类器:
denialSignatures识别「沙箱正常工作但拒绝了命令」(bwrap 的 EROFS、Landlock 的 EACCES、Seatbelt 的 EPERM);runnerFailureRules识别「沙箱 runner 自己没启动」。前者是策略拒绝,后者是基础设施故障,不能混为一谈。
审批流程
审批服务 ctx.approval 回答一个很窄的问题:这个具体操作可以继续吗?
它通过 approval/request waterfall 把问题分发给应答者(answerer)。应答者负责处理就返回结果,不负责就调用 next() 委托;第一个应答者占据唯一的决策槽位。结果类型是闭合的:
| 结果 | 含义 |
|---|---|
allowed-once | 一次性放行,只授权所询问的那一个操作 |
rejected | 明确拒绝 |
cancelled | 请求被撤回 |
unavailable | 无应答者、应答者抛异常或返回值不合规 |
关键安全性质:缺失应答者产生 unavailable,而不是放行。调用方对 rejected、cancelled、unavailable 一律执行拒绝。
每次询问都会记录一对仅日志的审计事件:approval/asked 和 approval/decided。它们不进入模型可见的 transcript——模型看到的是工具结果和运行时上下文快照,不是审批过程本身。
按会话的审批策略有两种:
| 策略 | 行为 | 场景 |
|---|---|---|
ask(默认) | 委托给应答者链;无应答者时回退 unavailable | 交互式 UI,需要人做决定 |
never | 确定性返回 rejected,不分发任何应答者 | CI、无人值守运行 |
Warning
never不是「永远允许」,而是「永远不弹审批,所有 ask 一律拒绝」。它是严格的无人值守姿态,不是放行开关。
权限预设
沙箱模式(sandbox/mode)和审批策略(approval/policy)是两个相互独立的旋钮(knob)。ctx.permissionPresets 把它们捆绑成具名预设,客户端(比如 Web 界面右上角的权限下拉框)只需选一个名字:
| 预设 | 沙箱模式 | 审批策略 | 含义 |
|---|---|---|---|
workspace-write | workspace-write | ask | 可写工作区,敏感操作问用户 |
danger-full-access | danger-full-access | never | 绕过沙箱、不询问 |
预设表是配置驱动的,默认自带上面两个;名字 custom 被保留给「派生的非预设状态」——当两个旋钮的组合不在表里时,界面显示 custom,但它不是可切换目标。
切换预设时,服务会追加一条仅日志的 permission/preset 事件,然后通过每个旋钮自己的 setter 写入。两个预设共享同一个旋钮组合时,这条事件让系统仍能分辨用户选的到底是哪一个。
信任模型
把这三层合起来看,dsh 的信任模型很直白:
- 默认信任人类:交互模式下
ask,人来做最终决策; - 默认不信任无人值守:
never确定性拒绝,机器应答者(如 ACP 桥接层)也只做一次性决策; danger-full-access只配给可丢弃环境:官方事故复盘明确提醒过,绕过沙箱的预设应只用于一次性、可丢弃的沙箱环境,比如 CI 的临时容器。
典型的审批交互长这样:模型想执行一条命令,UI 在已流式输出的工具调用卡片上挂出询问(通过 callId 关联,不重复渲染参数),你点「允许」放行这一次,点「拒绝」或让卡片过期,调用都以拒绝收场。
Tip排错时先分清是哪道闸门拦的:报
[sandbox: file access denied under <mode> mode]是沙箱策略拒绝;报审批超时或unavailable是审批链的问题。两者修法完全不同。
小结
- 沙箱管文件效果边界:
read-only/workspace-write/danger-full-access,失败关闭。 - 审批管单次授权:
allowed-once是唯一放行,缺应答者一律unavailable。 - 权限预设 = 沙箱 + 审批两个旋钮的具名组合,
custom是派生的非预设状态。 - 信任模型:交互问人,无人值守拒绝,全放行只给可丢弃环境。