DeepSeek Harness 的文件搜索:打包 ripgrep 与 unconfined spawn
在 deepseek-harness 里读沙箱那一层,很容易产生一个印象:这个仓库把「在本机执行东西」拆得非常细,ctx.sandbox 有三种模式、四个平台后端,可写根有唯一定义处,ctx.fs 之上还挂了一道围栏。读到这里你大概会默认:模型能碰文件系统的所有路径都被这套词汇罩住了。
然后你翻到 packages/fs/tool-fs-search/,会看到模型每天用得最多的两个工具 —— glob 和 grep —— 根本不在这套词汇里。它们不走 ctx.fs,也不注入 sandbox。README 自己给这条执行路径起的名字,原文就是 the unconfined spawn(packages/fs/tool-fs-search/README.md:5)。
先说清限定:以下全部来自 2026-08-16 我们对快照 47f9438 的静态阅读。这个仓库建立于 2026-08-13,版本是 0.1.0-rc.5,GitHub 上一个 Release 都没有,README 自述处于开发者预览阶段并明写未来会有破坏兼容性的变更。下面提到的每一个字段名、默认值、行号都可能在后续版本里变。我们没有安装过 dsh,没有跑过任何命令,没有起过任何一个沙箱。
一行 inject 决定了这两个工具的位置
packages/fs/ 下截至 2026-08-16 有 7 个包:-fs(seam)、-fs-local、-fs-observation-policy、-fs-sandbox、-tool-fs、-tool-fs-search、-tool-str-replace-editor。glob 与 grep 属于其中倒数第二个。
关键的一行在 packages/fs/tool-fs-search/src/index.ts:70:
export const inject = ['tools', 'systemPrompt', 'subprocess']
三个依赖,没有 fs,也没有 sandbox。对照着看另外那半边:packages/fs/fs-sandbox/ 提供的围栏是 read-only 拒绝一切变更、workspace-write 只允许目标规范化后落在 writableRoots() 里、danger-full-access 直接放行(packages/fs/fs-sandbox/README.md:15-17),而读在任何模式下都放行(同 README :5)。也就是说,即使 glob/grep 真的接在 ctx.fs 上,作为读操作它们也不会被这道围栏拦住;而现在它们连接都没接。
这两件事要分开记:一是「围栏管不管读」,二是「这条路径压根不经过围栏」。前者是围栏自己的语义,后者是装配上的事实。
「unconfined」不等于「什么约束都没有」
glob/grep 背后是 随包发布的 ripgrep 二进制(@vscode/ripgrep),不是系统里的 rg。每次调用通过 ctx.subprocess 用一组固定的 argv 向量 spawn 这个二进制,argv 前置 --no-config;README 给出的理由是防止宿主环境里的 RIPGREP_CONFIG_PATH 往这条 spawn 里注入 --pre 预处理器(packages/fs/tool-fs-search/README.md:5)。这个二进制的具体版本号我们没有核实 —— 没有读它对应的 package.json,所以这里不写。
那这条路径上还剩什么?答案是 ctx.subprocess 那一层自己的规矩,它们和沙箱是两套东西:
- argv 永不被 shell 解释(
docs/subsystems/subprocess.md:101)。这条对搜索工具尤其关键,因为 pattern 是模型给的。 - 环境变量按名字启发式清洗。
scrubbedParentEnv()从process.env里剔除命中SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i的名字,以及所有DSH_前缀的名字(packages/subprocess/subprocess/src/index.ts:44、:60-66)。JSDoc 明说PATH、HOME、locale、proxy 变量会活下来。packages/subprocess/subprocess-local/README.md:32自述这只是名字启发式,像*PASSPHRASE*这样名字不同的 secret 会继续传下去。 - seam 本身不提供任何默认值:「this seam applies no defaults: every disposition, limit, and directory is explicit」(
docs/subsystems/subprocess.md:91)。
所以 README 里那个 unconfined,指的是不受 ctx.sandbox 那套文件效果约束,而不是这条 spawn 裸奔。这个区别值得写清楚,因为两种误读的方向正好相反:一种以为搜索也被沙箱兜着,另一种以为搜索完全没有任何处理。
顺带一提,沙箱那套词汇本身的边界也是写在明面上的:docs/subsystems/sandbox.md:11 原文说「Network and process visibility are outside this vocabulary.」—— 网络和进程可见性从一开始就不在这套词汇里。
四个上限,以及唯一那个必须你自己选的
搜索侧一共有几个上限项,值得逐个记住确切名字与它写在哪一层(截至 2026-08-16 快照):
| 配置项 | 默认值 | 写在哪一层 |
|---|---|---|
sampleOverCapGlobResults | 必填,无回退 | 配置 schema,部署方必须显式给 |
globMaxResults | 100 | 包 README 的配置表 |
grepMaxMatches | 250 | 包 README 的配置表 |
grepMaxLineBytes | 2000 | 包 README 的配置表 |
SEARCH_TIMEOUT_MS(搜索超时) | 30_000 | 常量在 packages/fs/tool-fs-search/src/search-core.ts:42,同一个值又作为 schema 默认写在 src/index.ts:106 |
第一行是这张表里最特别的:sampleOverCapGlobResults 没有回退值,装插件时不给就过不去。其余几项都有默认,超时那一项还出现在两个位置 —— 一处是常量、一处是配置默认,两处的值一致。
这些默认值该不该改?我们不给建议。它们是配置里的默认值,不是你用起来会怎样的保证,仓库也没有给出通用取值指导,具体调成多少取决于你的用法。
超时这一块还有个相邻事实,我只并列陈述,不推断二者的调用关系:packages/guard/timeout-policy/(包名 @deepseek-ai/dsh-tool-call-timeout-policy)是零配置的,只读工具自己声明的 ToolDefinition.timeoutMs;它的 README 有一节标题就叫「Cooperative, not a hard kill」(:30-32),原文说派生的信号只做通知,终止仍属于工具本身,「a tool that ignores the signal will not stop on timeout」。同 README :56-57 记了一条限制:没有全局预算,只有声明了 timeoutMs 的工具才有 deadline,而 bash/read/write/edit 故意都不声明。这两处的具体链路我们没有在卡内材料里核到底,就到此为止。
「不是安全边界」这句话,仓库自己写了五处
这不是我们的评价,是原文集中在一起的样子:
packages/code-runtime/code-runtime-worker-thread/README.md:5:「Containment, not a security boundary」,并写「trust posture is bash-equivalent by design」。docs/subsystems/code-runtime.md:161:isolation字段是「a diagnostic label, not a security claim」。packages/fs/fs-sandbox/README.md:19的小节标题:「Threat model: a policy fence, not a kernel boundary」;同 README 还把围栏描述为「a check in TRUSTED code over a MODEL-CONTROLLED path」,并明说残留的 TOCTOU 被收窄但没有消除、并且是被接受的(:21、:43)。packages/extensions/tool-cordis/README.md:23:「The sandbox isolates globals but is not a security boundary.」packages/extensions/cordis-host-runner/README.md:32:同源表述。
把这五处和 tool-fs-search 的 unconfined 放在一起看,这些原文都是在划定边界的适用范围,而不是在承诺隔离。至于这样的姿态合不合适你的场景,我们不给结论;也请不要由这些原文反推「所以安全」或「所以不安全」。
顺着 ctx.fs 那一侧再补两条对照事实,能把「搜索」和「读写文件」的处置差别标得更清楚。其一,文件 IO 这边没有超时:read/write/edit 不收 timeoutMs,provider 契约不设 deadline,docs/subsystems/filesystem.md:272 给的理由是本地 syscall 至多是 best-effort 可中止,超时没法让进行中的 fsync/rename 停下来 —— 而搜索那边有一个 30_000 的常量。其二,ctx.fs 的错误码是封闭枚举、共 13 个(docs/subsystems/filesystem.md:254-267),其中 FS_SANDBOX_DENIED 是策略拒绝,与宿主内核拒绝的 FS_PERMISSION_DENIED 被明确区分开(:270)。这层区分只存在于走 ctx.fs 的路径上,而 glob/grep 不在这条路径上。
Windows 侧的一条并列记载
packages/sandbox/sandbox-windows-acl/README.md:93-102 记了若干已知限制,其中一条是:受限进程内 spawn(..., { stdio: 'pipe' }) 会 EPERM,因此「A confined process therefore cannot capture a grandchild’s output through a pipe; tools that must capture output cannot run confined.」
这条记载和 tool-fs-search 不注入 sandbox 是两处独立的仓库事实,我们只把它们并列摆出来,不推断谁导致谁、也不推断作者的取舍。你如果关心 Windows 上的实际行为,请以仓库最新的这两份 README 与代码为准。
另外,Windows 侧的 windows-acl 后端在未探测时声明的 enforcement 是 partial(packages/sandbox/sandbox-local/src/index.ts:177-187),其余三个后端 bwrap/landlock/seatbelt 声明的是 full。而 SandboxEnforcement 在 seam 文档里被定性为「a reported fact」,partial 意味着后端或较老内核 ABI 只能管住一部分文件效果(docs/subsystems/sandbox.md:30)。这些同样只是文件里写的话,不是运行观测。
你可以自己去核的三个动作
- 打开
packages/fs/tool-fs-search/src/index.ts:70,确认那行inject里有没有fs和sandbox。 - 打开
packages/fs/fs-sandbox/README.md:5与:15-23,把「读全放行」和威胁模型小节连起来读一遍。 - 对比两个示例组合的装配:
examples/acp-agent/cordis.yml里装的是@deepseek-ai/dsh-sandbox-local(:25)、dsh-bash-sandbox(:38)与dsh-fs-sandbox(:166);而examples/headless-agent/cordis.yml装的是@deepseek-ai/dsh-bash-local(:39)与@deepseek-ai/dsh-fs-local(:157),我们在这个文件里grep sandbox一条都没有匹配到。同一个仓库里两个示例的隔离姿态不一样,这一点只陈述,不延伸。
最后重复一遍开头的限定:这些行号、字段名与默认值都对应 2026-08-16 的快照 47f9438,版本 0.1.0-rc.5,README 自述开发者预览并明写会有破坏兼容性的变更。你读到本文时,上面任何一处都可能已经不是这样了 —— 请以仓库最新内容为准。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的代码执行残留:terminate 只结束线程
- DeepSeek Harness 的 terminal 工具:README 说 pty,代码是 terminals
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。