DeepSeek Harness ACP 会话绕过文件沙箱:preset 里的沙箱与权限行为什么不生效
如果你在 DeepSeek Harness(DSH)里把沙箱和权限都配在 agent preset 上,用 web 或 headless 会话验证一切正常——写工作区外路径会被明确拒绝——那很可能存在一个你没有察觉的缺口:换成 ACP 会话(dsh --profile acp)之后,同一个写入会直接成功落到工作区之外**。** 据报告者实测与社区核对源码,原因不是配置写错了,而是 ACP 会话不 join 任何 preset 组合(bundle 把 model-facing 的行留在 host plane),于是部署里放在 preset 的 sandbox / permission 行对它完全不生效;而看起来最像修法的那条路——「给 agentOptions() 补上字段转发」——在类型层面就不成立,因为底层的 AgentOptions 根本没有 sandbox 与 permission 字段(#9693)。本文给出一次性复现判据、为什么不能靠改 agentOptions() 修,以及在核心修好之前怎么用 host 侧策略兜住。
DeepSeek Harness 现象:同机同 profile,换个入口沙箱就失效
一句话结论:沙箱边界是否生效,取决于这个会话有没有参与 profile 的组合;ACP 会话不参与,所以它拿不到 preset 里的沙箱行。 判据非常干净——同一份 patch、同一台机器、同一个目标路径,只换入口:
| 会话入口 | 写工作区外路径 | 说明 |
|---|---|---|
web(dsh web) | 被拒绝 | 会话参与了 preset 组合,沙箱行生效 |
| headless | 被拒绝 | 同上 |
ACP(dsh --profile acp) | 写入成功 | 不 join preset,沙箱行不生效 |
据报告者实测,这三组的差异稳定可复现,且与工作区路径、写的内容都无关(#9693)。
DSH插件 机制:两件事叠加,缺一不可
根因是「组合缺失」,不是「字段没转发」——后者只是表象。 拆开看:
- 表层:
agentOptions()只透传{ provider, model }两个字段,看起来像「漏了几个字段」; - 真因:
AgentOptions这个类型本身就没有 sandbox / permission 字段,所以就算你想转发也无处可取; - 组合层:ACP bundle 把 model-facing 的行留在 host plane,ACP 会话不 join 任何 preset 组合,而部署里把沙箱与权限写在 preset 上——两者对不上(#9693)。
这个结论在社区核对源码时被逐条确认,包括 ACP 包自己的注释原文与 AgentOptions 的类型定义(#9693)。
DeepSeek Harness 三步复现:确认你的部署是否踩中
- 准备一个工作区外的靶标路径。 例如在工作区之外建一个空目录
D:\dsh-outside,确认它不在工作区内(工作区路径见设置 → 工作区)。 - 用 web 会话做对照。 在
workspace-write的 web 会话里让模型执行一次写入:
Set-Content -Path "D:\dsh-outside\probe.txt" -Value "probe"
# 预期:被沙箱拒绝,结果里带 file access denied 之类标记,文件未创建
- 换 ACP 会话做同一件事。 用
dsh --profile acp起一条会话,执行完全相同的命令,预期:文件被创建,且结果里没有任何 sandbox 标记。两组结果不一致即复现(#9693)。
复现完记得删掉靶标文件;这类测试的目的只是判定「边界在不在」,别把它当成常规验证手段留在脚本里。
DSH插件 为什么「给 agentOptions 加字段」不是修法
一句话结论:那条路在类型层面就不成立——AgentOptions 没有这两个字段,你得先让 ACP 会话参与 profile 的组合,配置才有地方生效。 这一条值得单独讲,因为它是这类问题里最容易走偏的方向(#9693)。
正确的修法方向有三条,按落地难度从高到低:
- 让 ACP 会话参与组合(核心侧):把沙箱与权限行纳入 ACP 会话的组合路径,让它和 web/headless 走同一套 profile 组合逻辑。这是根治,但属于核心侧改动。
- 把策略下沉到 host 侧:在宿主侧用插件做工具前置检查,让「越界写」在分发阶段被拒绝并给出可读提示。这条见效最快,但只覆盖本插件能看到的工具与路径形态。
- 临时降低依赖:在 ACP 场景下避免把「沙箱边界」当作安全前提——也就是说,不要把 ACP 入口暴露给不可信输入,直到核心修好。
讨论中报告者采用的正是第 2 条,并明确标注了它的局限:它只是 policy 边界,不是 capability 边界——tools/pre-execute 拦得住工具调用声明的路径,但覆盖不到 shell 命令行里拼出来的路径,也覆盖不到 MCP 通道;进程实际拥有的权限并没有被改变(#9693)。
DeepSeek Harness host 侧兜底:pre-execute 路径策略怎么写
思路是在工具分发之前,把「读起来在工作区内、解析后落在工作区外」的路径挡掉。 三步:
- 新建一个最小的 host 侧插件,挂在
tools/pre-execute上,对文件类工具声明的路径参数做一次 realpath 解析与工作区包含判断; - 在 profile 的 patch 层挂载它:
- insert:
- id: acp-path-policy-guard
name: './plugins/acp-path-policy-guard'
- 验证:在 ACP 会话里重跑第 2 步的越界写入,预期这次被插件拒绝,并在结果里给出「这是策略拒绝、不是命令错误」的说明。验证时同时用 shell 拼一条路径绕一绕(例如在命令行里用变量拼出
D:\dsh-outside),确认你能接受它绕过——如果不接受,说明你还得补核心侧那条路(#9693)。
记住这不是修复:它把「静默越界」变成「显式拒绝 + 提示」,但沙箱本身仍然对 ACP 会话不生效。不要把 pre-execute 当成沙箱的替代品来对外承诺安全边界。
DSH插件 排查注意事项
这条问题的关键在于先问「这个会话走的是哪个入口」,而不是先怀疑配置。 六条要点:
- 同机同 profile 换入口对照是性价比最高的一步:web 被拒、ACP 通过,基本就定向了(#9693)。
- 沙箱配置写在哪一层决定它对谁生效:写在 preset 层的配置,只有参与组合的会话才拿得到;另一类「策略看着对、实际没按预期生效」是权限升级被拒(not strictly wider),见 DSH plugin 沙箱权限升级被拒(#9693)。
- 不要用
agentOptions()作为落点:缺的不是转发,而是这一层根本没有承载这两个字段的位置(#9693)。 - pre-execute 是策略层、不是能力层:用它兜底可以,用它承诺安全不行(#9693)。
- 对外暴露 ACP 入口前先做一次越界测试:这类「配置看起来对、实际不生效」的缺口,只有行为对照能发现。
- 修复状态如实:截至
0.2.0-rc.2,ACP 会话不参与 preset 组合的行为仍存在;报告者的 host 侧插件是用户侧兜底,不是官方修复,也没有把沙箱补到 ACP 通道上。
这类「入口不同、策略不同」的缺口,往往和插件挂载、配置层级纠缠在一起,排查时很容易绕远。如果你想先把插件侧变量收干净,与其在各处手敲命令核对,不如用 DSH Plugin Hub 这类可视化插件市场——插件装卸、更新确认、诊断与系统日志都在一个面板里,能让你先确认「当前环境到底挂了哪些插件、哪些配置行」。

来源:Discussion #9693。
常见问题
同一份 DeepSeek Harness 配置下 web 会话被拒、ACP 会话却能写工作区外,是因为沙箱与权限配置放在 agent preset 里,而 ACP 会话不 join 任何 preset 组合——它把 model-facing 的行留在 host plane,所以 preset 里的 sandbox 与 permission 行对 ACP 会话根本不生效。测试办法是在同一台机器上分别用 web 与 ACP 会话写同一个工作区外路径,前者被拒、后者成功,即可复现。
给 DeepSeek Harness 的 ACP 会话补 sandbox 与 permission 配置,靠在 agentOptions() 转发字段是修不了的,这是落点选错了。据社区核对源码,agentOptions() 只透传 provider 与 model,而底层的 AgentOptions 类型本身就没有 sandbox / permission 字段可转发,所以「往这个方法里加字段」在类型层面就不成立。真正的修法是让 ACP 会话也参与 profile 的组合(composition),或把策略下沉到 host 侧的工具前置检查。
在 DeepSeek Harness 的 host 侧用 tools/pre-execute 做路径策略,只能当策略边界,不能当 capability 边界。pre-execute 是一层「分发前拒绝」的门槛,它拦得住本插件能看到的工具与路径形态,但覆盖不到 shell 命令行里拼出来的路径以及 MCP 通道,也不改变进程实际拥有的权限。适合做「额外一层可读的拒绝与提示」,不适合用来替代沙箱。
判断 DeepSeek Harness 的沙箱设置有没有对当前会话生效,做一次行为对照即可:在同一条会话里分别尝试写工作区内与工作区外的同一个相对结构,若工作区外也成功、且没有任何 sandbox 标记,说明当前会话没有拿到沙箱策略。第二步是确认这个会话走的是哪个入口(web / headless / ACP),因为 ACP 入口不 join preset,preset 层的配置对它不生效是设计使然而不是配置写错。
相关术语
- ACP(Agent Client Protocol)
- ACP 是 DeepSeek Harness 用来对接外部客户端的一类会话入口,通过 dsh --profile acp 启动,把会话能力暴露给外部宿主使用。它的 profile 组合方式与 web/headless 入口不同,因此部署里放在 agent preset 中的沙箱与权限行不会自动作用于 ACP 会话。— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- agentOptions()
- agentOptions() 是 DeepSeek Harness 里构造子会话启动参数的方法,当前只透传 provider 与 model 两个字段。由于底层 AgentOptions 类型本身没有 sandbox 与 permission 字段,所以「往这里补转发」在类型层面就不成立,沙箱配置必须靠 profile 组合提供。— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- agent preset
- agent preset 是 DeepSeek Harness 中一组预先组合好的 agent 行(包含模型、工具、沙箱与权限等配置),会话创建时会按预设把这些行组合进运行时。它在 profile 的 preset 层生效的前提是会话真的参与了这次组合,ACP 会话恰好不参与。— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- tools/pre-execute
- tools/pre-execute 是 DeepSeek Harness 在工具分发之前触发的钩子,插件可在这一步检查参数、拒绝调用或改写上下文。它常被用来做策略层的兜底(例如路径越界检查),但属于策略边界而非 capability 边界,不能替代沙箱对进程权限的限制。— https://github.com/deepseek-ai/deepseek-harness/discussions/9693