DeepSeek Harness 在 exFAT 工作区 ACL 授权成功却没效果?沙箱与版本令牌排查
在 DeepSeek Harness 的 exFAT / FAT32 工作区上,ACL 沙箱的 grant 会返回成功,但卷上根本不保存 DACL,受限子进程照样能写工作区;同一类卷还会让「文件已变」的版本令牌退化成 size 加 2 秒 mtime。 两个失效都不报错:前者是安全边界失效,后者是数据完整性缺口,排查时必须分开看,也不是某个 DSH插件 的配置问题。据讨论,报告者在 Windows 与 exFAT 工作区上实测,社区成员随后逐行核对了 sandbox-windows-acl 与 session-persistence-jsonl 的源码(讨论原文)。
DeepSeek Harness 在 exFAT 工作区上「授权成功但零效果」是什么样
表现是 grant 不报错、工具调用不被拒绝、工作区文件仍可被任意写入,三项同时成立。 按下面的顺序确认,别一上来就改配置:
- 在 PowerShell 执行
Get-Volume -DriveLetter E | Select-Object DriveLetter,FileSystem,预期看到exFAT或FAT32;若显示NTFS,本文两条失效都不适用。 - 确认工作区根到底是卷根还是子目录:
(Get-Item E:\proj).FullName。据讨论,包内 README 对「grant root 本身是 FAT 卷」与「granted roots 之外的 FAT 目标」给了两句不同的话,结论要按实际层级收窄。 - 让 Agent 在工作区里新建一个文件,再用另一个进程改写它,预期两个动作都不被拦截。
- 执行
icacls E:\proj记录一次输出,触发一次 grant 后再记录一次,预期两次完全一致——没有新增 ACE,说明授权没有落到卷上(社区核对源码)。
exFAT 上的写入失败还有另一副面孔:EISDIR 与盘根 EPERM mkdir,见 exFAT 工作区写入报 EISDIR 排查。
同一台机器上换成 NTFS 目录做对照,差别会非常直接:
| 观察项 | exFAT 工作区 | NTFS 工作区 |
|---|---|---|
| grant 返回值 | 成功 | 成功 |
icacls 前后差异 | 无 | 有新增 ACE |
| 受限子进程写工作区 | 允许 | 拒绝 |
为什么 grant 不报错:它只兜 Win32 错误,不探测卷能力
grant.ts 的 fail-closed 只覆盖「Win32 调用返回错误」这一种情况,而 exFAT 上 SetNamedSecurityInfoW 返回的是 ERROR_SUCCESS。 没有错误可抛,throwLastError 和 AggregateError 就没有触发点。社区核对源码后的链条是:
packages/sandbox/sandbox-windows-acl/README.md的:190写着「FAT 卷作为 grant root 会 fails loudly」。- 同一份 README 的
:204又写着「对 FAT 类卷的 warn-only 姿态已记录但未实现」,两句话互相矛盾。 grant.ts里没有GetVolumeInformation,也没有任何卷能力探测;实现把「调用成功」等同于「DACL 已写入」。- exFAT 没有安全描述符存储,调用不报错也不保存,受限令牌拿不到任何限制;于是失败既不响亮,也不可观测。
这说明问题不是某个分支写错,而是「README 承诺需要的能力探测」在实现里根本不存在。修复方向也随之确定:把探测补上,而不是在写入失败时再猜原因。
版本令牌为什么漏检外部改动:五元组只剩两项有效
会话持久化的物理 revision 是 dev:ino:size:mtimeNs:ctimeNs,在 2 秒时间戳、按卷常量 ctime、inode 可回收的卷上,真正还能区分改动的只剩 size 和 2 秒粒度的 mtime。 这条与沙箱无关,属于独立的完整性缺陷。复现:
- 在工作区放一个测试文件,记录大小,然后让 DSH 读取一次。
- 在同一个 2 秒窗口内用另一个工具写回相同大小的内容,预期
mtimeNs的纳秒部分为 0、秒级时间戳不变。 - 回到 DSH 继续编辑同一文件,预期不会出现「文件已被外部修改」的拦截,改动被直接覆盖。
- 把测试内容换成不同大小再重复一次,预期这次能触发拦截——这能证明守卫没坏,坏的是令牌分辨率。
五元组各分量在这类卷上的实际贡献:
| 分量 | 在 exFAT 上的表现 | 是否还能区分改动 |
|---|---|---|
dev | 同一卷内恒定 | 否 |
ino | 删除重建后可能复用 | 不可靠 |
size | 精确 | 是 |
mtimeNs | 2 秒量化,纳秒位为 0 | 仅同窗口外 |
ctimeNs | 每卷常量 | 否 |
备份还原、同步盘、杀毒软件重写这类外部动作,在 2 秒粒度卷上都可能被静默放行。
路径一:DeepSeek Harness 的 grant 前查 FILE_PERSISTENT_ACLS,不支持就 fail-closed
最省事的修法是在授权入口加一次卷能力探测,把「不支持」变成采用阶段的明确错误,而不是运行期的静默放行。 步骤:
- 在 grant 入口对 grant root 调用
GetVolumeInformationW,读取返回的文件系统标志位。 - 判断
FILE_PERSISTENT_ACLS(0x00000008)是否置位。 - 未置位时走与现有
throwLastError相同的失败路径,错误信息里带上卷路径和「不支持持久 ACL」。 - 改动示意(不是仓库原代码):
const flags = volumeFlags(root) // GetVolumeInformationW 的输出
if ((flags & FILE_PERSISTENT_ACLS) === 0) {
throw new Error(`ACL sandbox unsupported: ${root} does not preserve ACLs`)
}
- 用 exFAT 工作区重新打开一次,预期采用阶段就报错;再在 NTFS 工作区重复一次,预期行为与改动前一致。
路径二:把 DeepSeek Harness 工作区迁到 NTFS 卷
如果这份工作区必须保留写权限隔离,换卷比打补丁彻底:NTFS 会真正保存 DACL,五元组令牌也恢复分辨率。 步骤:
- 执行
robocopy E:\proj C:\work\proj /E /COPY:DAT /R:1 /W:1,预期退出码小于 8。 - 执行
Get-Volume -DriveLetter C | Select-Object FileSystem,预期为NTFS。 - 在 DSH 里移除旧工作区,重新把
C:\work\proj添加为工作区。 - 重复 icacls 对照:grant 前后应出现新增 ACE;再跑同尺寸改写测试,预期这次会触发「文件已变」拦截。
路径三:关掉 DeepSeek Harness 的 ACL 沙箱,把工作区降为只读
既不想换盘、又不想改宿主代码时,正确姿势是承认这条边界不存在,然后收回写权限,而不是继续假装沙箱生效。 步骤:
- 在「设置 → 插件」里停用 ACL 沙箱对应的插件行,重启后预期日志不再出现 grant 记录。
- 把工作区指向只读来源:写保护的 U 盘、只读共享,或 NTFS 上仅授读取权限的目录(
icacls C:\ro /grant "%USERNAME%:(OI)(CI)(RX)")。 - 让 Agent 做一次读、一次写,预期读成功、写在文件系统层被拒,且报错不来自沙箱。
- 把这条件写进团队说明:丢失的是工作区写能力,换来的是不再有「以为被隔离」的错觉。
DeepSeek Harness 三条路径各自的复测项
三条路径解决的不是同一件事:路径一让失败变响,路径二恢复真实隔离,路径三放弃写能力。 按你选的那条逐项验:
- 路径一:exFAT 上采用即报错;NTFS 上 grant 前后
icacls有差异;原有 NTFS 写入流程不受影响。 - 路径二:同尺寸外部改写能触发拦截;受限子进程写工作区被拒;会话文件不再出现静默覆盖。
- 路径三:写操作在文件系统层被拒;读、列目录、检索仍可用;不要再对外宣称工作区受 ACL 保护。
- 无论选哪条,都把版本令牌的退化单独记录——它不会随沙箱修好而消失,只有换到 NTFS 才会一起恢复。
常见问题
DeepSeek Harness 的 grant 在 exFAT 上返回成功却零效果,因为 exFAT 不保存 DACL,SetNamedSecurityInfoW 返回 ERROR_SUCCESS 但什么都没写进卷里,受限子进程实际没有受到任何约束。grant.ts 的 fail-closed 只处理 Win32 调用报错的情况,没有报错就没有落点。
在 DeepSeek Harness 的 grant 之前调用 GetVolumeInformationW,检查返回标志里的 FILE_PERSISTENT_ACLS(0x00000008)是否置位。未置位说明该卷不保留、不强制 ACL,应直接按不支持处理并抛错,而不是继续授权。
DeepSeek Harness 的会话版本令牌是 dev:ino:size:mtimeNs:ctimeNs 五元组,在 2 秒时间戳精度的卷上纳秒位恒为 0、ctime 是卷常量、inode 还可能被回收。同尺寸、同 2 秒窗口内的外部改写,五元组算出来一样,守卫不会开火。
可以,给 DeepSeek Harness 的 grant 入口加一次卷能力探测即可,代价是该工作区在 exFAT 上直接不可用。这是 fail-closed 的取舍:宁可采用阶段失败,也不要运行期给出「已隔离」的假象。
相关术语
- FILE_PERSISTENT_ACLS
- FILE_PERSISTENT_ACLS 是 `GetVolumeInformationW` 返回的文件系统标志位(0x00000008),表示该卷保留并强制执行访问控制列表。FAT/exFAT 不置位,NTFS 置位。— https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw
- DACL(Discretionary ACL)
- DACL 是安全描述符中决定「谁能对这个对象做什么」的访问控制列表,由 ACE 组成。它必须由文件系统持久保存才能生效,exFAT 没有这项存储能力。— https://learn.microsoft.com/en-us/windows/win32/secauthz/access-control-lists
- file version token(文件版本令牌)
- file version token 是持久化层用来判断文件是否被外部改动的物理版本标识,当前实现取 `dev:ino:size:mtimeNs:ctimeNs` 五元组。时间戳精度和 inode 稳定性都会直接影响它的判别力。— https://github.com/deepseek-ai/deepseek-harness/discussions/10216
来源
- deepseek-harness Discussion #10216:FAT/exFAT 卷上 ACL 沙箱授权成功但零效果、版本令牌漏检外部改动· deepseek-ai(GitHub Discussions)
- deepseek-harness 仓库:核对 sandbox-windows-acl 与 session-persistence-jsonl 源码· deepseek-ai(GitHub)