🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

写入保护 azazo1/dsh-write-protect

DeepSeek Harnessv0.3.0
azazo115 天前有提交已提供中文文档扫描:含敏感能力

防止模型改写工作区里的指定路径. 同时支持放开某些外部目录的写入而不必放开沙箱.

36.2/100
综合分
4
GitHub 星标
312
npm 周下载
—/5
用户评分

数据截至 2026/10/20 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-write-protect
npm 源 · dsh-write-protect 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · security
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-write-protect @ 0.3.0
✓Node 引擎要求 >=20.11 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/1 18:25:53

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 14 处,展示前 14 条)
  • 读取本地文件
    lib/policy.mjs:164
    return (await lstat(path)).isDirectory();
  • 读取本地文件
    lib/policy.mjs:175
    return await readdir(path, { withFileTypes: true });
  • 读取本地文件
    lib/policy.mjs:650
    if (!lstatSync(target).isFile()) return notRegular;
  • 读取本地文件
    lib/policy.mjs:671
    return parseReadOnlyFile(readFileSync(fd, { encoding: "utf8" }), target, workspaceRoot, maxEntries, warnings);
  • 读取本地文件
    lib/policy.mjs:1046
    if (!existsSync(workspaceRoot)) return;
  • 发起网络请求
    lib/policy.mjs:1314
    const outcome = await approval.request({
  • 读取本地文件
    lib/policy.mjs:1357
    return lstatSync(path).isDirectory();
  • 读取本地文件
    lib/provider.mjs:256
    if (existsSync(path)) binds.push("--bind", path, path);
  • 读取本地文件
    lib/provider.mjs:272
    if (existsSync(path)) binds.push("--bind", path, path);
  • 读取本地文件
    lib/provider.mjs:285
    for (const path of paths) if (existsSync(path)) binds.push("--ro-bind", path, path);
  • 读取本地文件
    lib/provider.mjs:299
    if (existsSync(path)) grants.push("--rw", path);
  • 发起网络请求
    lib/client.js:168
    fetch(PREVIEW_PATH, {
  • 发起网络请求
    lib/client.js:617
    const response = await fetch(GRANTS_PATH, {
  • 读取本地文件
    lib/containment-CwZXGzMh.mjs:77
    return await stat(path, { bigint: true });
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-store@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-ui-plugin-manager@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-session@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-fs@deepseek-ai/dsh-fs-local@deepseek-ai/dsh-fs-sandbox
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

1 张 · 点击查看大图

功能亮点

  • 防止改写指定路径
  • 放开外部目录写入
  • 无需放开沙箱
v0.3.02026-10-03
· dsh-write-protect v0.3.0 Date: 2026-10-03 这一版把引擎版本线从 @deepseek-ai/dsh-* 0.1.7 抬到 0.2.0-rc: peer 写成 >=0.2.0-rc.1 =0.2.0-rc.1 =0.2.0-rc.1 <0.3.0. 迁移方式: 先把 DSH 升到 0.2.0-rc.1 或 0.2.0-rc.2; 仍在 0.1.7 引擎线上的部署继续用 v0.2.0, 不要升到本版. · Upgrade Notes 1. 升级前确认引擎版本: 本版要求 @deepseek-ai/dsh-* 不低于 0.2.0-rc.1, 且仍在 0.2.x 上. 停在 0.1.7 的部署请继续用 v0.2.0; 更早的部署请继续用 v0.1.1. 2. 从 GitHub 固定版本安装时把 tag 换成 v0.3.0; 走 releases/latest 的预构建包会在本 Release 发布后自动指向这一版. 3. 升级后打开会话区的 "写入权限" tab, 确认本会话授权列表, 撤回与手动添加可用. 4. 验证方式: 让模型写 .git 里的文件, 应当被拒; 在面板里手动加一条临时可写根后再写, 应当放行; 撤回后模型应收到通知, 再次写入应当被拒. · Features · 会话写入权限面板: POST /api/dsh-write-protect.grants, 一次请求一个动作: list 列本会话授权, add 手动加临时可写根, revoke 撤回并投通知. · 路径解析与硬保护复用模型申请那一套: 工作区内的目标记保护旁路 (writableOverrides), 工作区外记额外可写根 (writablePaths), 规则文件本身仍不受理. · 撤回通知走 agent.inject, 以 write-protect 来源与 notice 形态排进模型下一步, 空闲会话不唤醒; 消息按 user/message 落进会话日志, 所以 dsh 重启, 同一会话恢复后模型仍看得到. · client 半区注册会话区 tab (conversation.view, order 20): 列出授权与 maxGrants 占用, 逐条撤回, 手动加临时可写根, read-only 模式提示, 并如实显示通知是否投出. · Known Limitations · 授权仍是会话级内存态: 面板加的临时可写根与模型申请批准的一样, 不跨会话, 也不写配置; 重启 dsh 后消失. · 撤回不影响已经跑起来的命令: 命令侧的挂载与 profile 在启动时就定好了. --- Full Changelog:
v0.2.02026-09-25
· dsh-write-protect v0.2.0 Date: 2026-09-25 这一版把写入保护从 "一份静态清单" 扩成 "可声明, 可申请, 可保鲜" 的一整套: 工作区根可以放一份只读规则文件, 模型能在任务确实需要时申请本会话的可写授权, 命令侧的清单也会跟着工作区变化及时重算. 配置入口从 Web 设置页的独立页面迁到插件页的配置卡片, 引擎版本线抬到 @deepseek-ai/dsh-* 0.1.7-rc.1 —— 仍在更早引擎线上的部署请先读 Upgrade Notes, 继续使用 v0.1.1. · Highlights · 新增 request_writable_path 工具: 任务要反复写同一片受保护区域时, 由模型提出申请, 你在审批弹窗里逐次决定; 授权按路径生效并覆盖其下全部后代, 且只在本会话内存里. · 新增工作区只读规则文件 (默认 .readonly): 与 "保护路径" 同语义, 随工作区走, 因此可以配成 "整个工作区只读 + 逐个目录批准"; 这份文件自身是唯一硬保护, 任何授权都不放行. · 命令侧清单开始保鲜: macOS 上字面条目改按 Seatbelt 正则拒绝, 会话中途 git init 出来的 .git 也挡得住; 全平台按 agent 运行状态监听工作区, 变化之后立即重算. · 配置迁到插件页卡片: 两个多行文本, 规则文件名与各项上限, 三个开关, 刷新下界与上界都在 dsh-write-protect 卡片上, 预览面板按草稿展开, 不必先保存. · danger-full-access 下完全不再介入 (含规则文件硬保护), 与 "保护只在 read-only / workspace-write 下生效" 的口径对齐. · Breaking Changes · 引擎版本线: 依赖抬到 @deepseek-ai/dsh-* 0.1.7-rc.1 (cordis ^4.0.4), 配置面改用 volatile Config 与 configForms, 并新增 @deepseek-ai/dsh-tools 这条 peer. 迁移方式: 先把 DSH 升到 0.1.7-rc.1 一档; 仍在 0.1.6 及更早引擎线上的部署继续用 v0.1.1, 不要升到本版. · 配置入口迁移: Web 设置页里独立的 "写入保护" 页被移除, 改由插件页的 dsh-write-protect 卡片承载. 旧页面保存过的值 (保护路径, 额外可写根, broker 加固开关) 走的是 settings namespace, 本插件不再读取; patch 层的 readOnlyPaths / writablePaths / hardenBroker 照常作为部署 base 生效. 迁移方式: 打开插件页, 在卡片上按 Upgrade Notes 重新填写. · danger-full-access 行为变更: 该模式下不再应用写保护围栏, 也不再应用规则文件硬保护 (此前 write / edit 仍会拒绝保护路径). 迁移方式: 需要保护就改用 read-only 或 workspace-write, 这个模式的语义就是 "不设限". · 预览不再回退部署工作区根: 没有当前会话 (或会话记录里没有 cwd) 时预览直接报错, 不再拿 dsh web 的启动目录去展开. 迁移方式: 先在界面上选中一个会话再点预览. · Upgrade Notes 1. 升级前确认引擎版本: 本版要求 @deepseek-ai/dsh-* 0.1.7-rc.1 一档 (peerDependencies 同号). 停在这条线之前的部署请继续用 v0.1.1. 2. 升级后打开插件页, 在 dsh-write-protect 卡片上重新填写保护路径与额外可写根, 并按需调整规则文件名, 各项上限与三个开关; 旧设置页保存过的值不会被读取. 保存后实时生效, 不用重启 dsh web. 3. 若之前依赖 "danger-full-access 下 write / edit 仍然挡保护路径", 请把模式改回 read-only 或 workspace-write. 4. 想在项目里用 "按需申请" 的玩法: 在工作区根放一份 .readonly, 用 (或 /) 保护整段工作区, 再让模型按目录逐个申请; 注意这份文件本身任何工具与命令都改不了, 要改只能在 DSH 之外编辑, 或换掉 readonlyFileName. 5. 验证方式: 让模型写 .git 里的文件, 应当被拒并提示可以申请授权; 让它在会话中途执行 git init 后立刻写 .git, macOS 与 bwra
v0.1.12026-09-12
· dsh-write-protect v0.1.1 Date: 2026-09-12 这一版堵上两处能绕过只读保护的漏洞, 并修掉 Windows 上额外可写根的解析错误. macOS 用户升级后 broker 逃逸加固默认开启, 如果确实需要从沙箱内驱动宿主 GUI, 请先读 Upgrade Notes. · Highlights · macOS: 堵住 open 经 launchd 把命令挪到沙箱外执行的逃逸, deny file-write* 不再被整条绕开, read-only 下同样生效. · Windows: 额外可写根里的 \ 不再被当作转义符, C:\Users\me\caches 这类路径不会退化成落点不确定的盘符相对路径. · glob 展开去掉 5000 节点预算, 改为按目录剪枝, 并停止跟随目录符号链接, 超大工作区不再漏掉深层路径. · 设置页新增 "macOS broker 逃逸加固" 开关, 需要从沙箱内驱动宿主 GUI 时可以关掉. · Features · 设置页: 新增 macOS broker 逃逸加固开关, 对应 patch 的 hardenBroker, 缺省开启; 关掉后 provider 原样返回官方 argv. · 保护路径与额外可写根: 补上 Windows 写法说明, 保护路径的绝对条目写作 //C:/Users/me/secret, 额外可写根按字面反斜杠解析. · 市场展示: 仓库内声明截图并内嵌到 README 的设置页一节. · Bug Fixes · macOS: 官方 profile 的 (allow default) 让沙箱内一条 open x.app 经 launchd 派生出不继承 profile 的进程, 从而在沙箱外任意写入; 现在在 profile 末尾追加 (deny mach-lookup (global-name-prefix "com.apple.coreservices")), (deny appleevent-send) 与 (deny mach-priv-task-port). SBPL 按 last-match-wins 解释, 追加在末尾才能盖过 allow; 名称过滤器按 reverse-DNS 分段匹配, 收窄到子服务无效, 只能整段拒绝. · macOS: 加固不再只认 workspace-write, 因为 read-only 的官方 profile 形状相同, 同样是 (allow default). · Windows: 额外可写根解析改为平台感知, \ 只在 POSIX 上转义, Windows 上按分隔符原样保留; ~\caches 与 ~/caches 一样展开为家目录, 展开, 盘符判定与工作区包含比较统一走 win32 路径 API. · Windows: 盘符相对路径 (C:caches) 改为拒绝并告警, 它的落点取决于进程当前目录, 会静默给出调用方从未指定的可写根. · Windows: 保护路径的 glob 元字符检测不再跳过 \ 后面的字符. · glob 展开: 目录性检查改用 lstat, 指向目录的符号链接不再走进去, 避免 Applications -> /Applications 这类链接把大项目启动扫卡住. · Performance · glob 展开: 删除 EXPAND_NODE_BUDGET, 超大工作区不再因节点预算截断而漏掉深层路径; 已经会被保护的目录不再往里扫, 被 ! 放行的目录仍继续展开. · Known Limitations · Windows 上 bash / pwsh 既挡不住保护路径, 也拿不到额外可写根; write / edit 两者都生效. · Linux 落到 Landlock 时无法单独保护子路径, 命令按官方沙箱跑并告警一次; 额外可写根可以加 --rw. · danger-full-access 下 bash 不进沙箱; write / edit 仍然挡保护路径. · 通配只覆盖展开当时已存在的路径; 字面条目即使尚不存在, write / edit 也会拒绝. · 展开结果缓存 5 秒, 逐次 tool call 复用. · broker 加固是纵深加固: 它关掉的是 launchd / LaunchServices 这条代理通道, 沙箱内的进程仍可通过其他本地守护进程 (Docker socket, ssh-agent 一类) 让沙箱外的服务代劳. · Upgrade Notes 1. 固定版本安装 dsh plugin --profile web add azazo1/dsh-write-protect#v0
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-write-protect 给 DSH 沙箱补上工作区里某一段路径的只读保护,典型用途是不让模型改 .git。也可以在 workspace-write 下声明工作区外的额外可写根,让 bash 与 write / edit 写到相邻目录,而不必切到 danger-full-access。还能声明读拒绝路径:命中的位置 read / grep / glob 这些工具与沙箱命令都读不到,需要时由模型申请本会话授权,同一条授权同时放开那条路径的读与写。 工作区根还能放一份只读规则文件(默认 .readonly),与插件页配置里的保护路径同语义;任务要反复写同一片受保护区域时,模型也可以申请本会话的可写授权(见下文两节)。会话区还有一个「写入权限」tab:在那里可以手动加临时可写根,也可以把某条授权撤回,撤回会通知模型。 write / edit 工具在所有平台都会挡住保护路径,并放行额外可写根。bash 等命令在 Linux / macOS 上同样生效;Windows 上 bash / pwsh 既挡不住 .git,也拿不到额外可写根。读取默认不受影响,只有配了读拒绝路径才会被挡(见「读拒绝路径」一节)。
展开完整文档(README 剩余 1 节)
dsh-write-protect

给 DSH 沙箱补上工作区里某一段路径的只读保护,典型用途是不让模型改 .git。也可以在 workspace-write 下声明工作区外的额外可写根,让 bash 与 write / edit 写到相邻目录,而不必切到 danger-full-access。还能声明读拒绝路径:命中的位置 read / grep / glob 这些工具与沙箱命令都读不到,需要时由模型申请本会话授权,同一条授权同时放开那条路径的读与写。

工作区根还能放一份只读规则文件(默认 .readonly),与插件页配置里的保护路径同语义;任务要反复写同一片受保护区域时,模型也可以申请本会话的可写授权(见下文两节)。会话区还有一个「写入权限」tab:在那里可以手动加临时可写根,也可以把某条授权撤回,撤回会通知模型。

write / edit 工具在所有平台都会挡住保护路径,并放行额外可写根。bash 等命令在 Linux / macOS 上同样生效;Windows 上 bash / pwsh 既挡不住 .git,也拿不到额外可写根。读取默认不受影响,只有配了读拒绝路径才会被挡(见「读拒绝路径」一节)。

官方沙箱只有「整个工作区可写」和「全只读」两档,管不到工作区内部的某一段,也不能把工作区外的个别目录并进 allow-list;Codex 一类实现默认会保护 .git,本插件补这一块。

安装

Web 端装进 web profile:

dsh plugin --profile web add azazo1/dsh-write-protect

固定版本:

dsh plugin --profile web add azazo1/dsh-write-protect#v0.3.0

GitHub Release 同时挂不带版本号的预构建包,安装时跳过 allowBuilds:

dsh plugin --profile web add https://github.com/azazo1/dsh-write-protect/releases/latest/download/dsh-write-protect.tgz

桌面端装进 desktop profile。它由 Electron 应用独占管理,dsh plugin 会拒绝 --profile desktop,所以要用应用内的插件管理器:在插件页的安装入口填上面命令里对应的包名或 tarball 地址。装上后重启应用,窗口刷新一次。

安装后会接管沙箱策略、write / edit 围栏与读拒绝的工具层围栏,Linux / macOS 上还会接管命令沙箱。改配置即时生效,不用重启 dsh web。web 与 desktop 两个 profile 跑的是同一套 Web 应用,桌面端只是多起一个 Host 子进程并给 打上平台标记,所以同一份包在两边通用,不需要分别构建。

引擎版本线要求 @deepseek-ai/dsh- 不低于 0.2.0-rc.1,且仍在 0.2.x 上(peerDependencies 与 devDependencies 都写作 >=0.2.0-rc.1 <0.3.0)。已在 0.2.0-rc.2 上日常验证。官方 SandboxProvider.confine() 自 0.1.6-alpha.1 起改为异步(Promise<ConfinedArgv> 加 signal 参数),插件配置自 0.1.7 起走 volatile Config(插件页的配置卡片经 configForms 读写);本插件的覆写与配置面都按这条线走。仍在 0.1.7 引擎线上的部署请继续用 v0.2.0,更早的部署请继续用 v0.1.1。

配置

保护路径、额外可写根、规则文件名、各项上限与三个开关的默认值都定义在 src/constants.ts 的 DEFAULT_,patch 的 policy 行与插件页保存的值都由它们兜底;需要部署级覆盖时在 patch 行显式给出字段(整行替换,mode / workspaceRoot 必须带上):

- id: dsh-write-protect-policy
name: dsh-write-protect
config:
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
workspaceRoot: !!js process.cwd()
部署级覆盖示例.
readOnlyPaths: ['.git', '//etc/pki']
writablePaths: ['../shared-scratch', '//tmp/dsh-extra']
readDenyPaths: ['secret', '//etc/pki']
hardenBroker: false
hardenWsl: false
readonlyFileName: '.readonly' # 置空即关闭工作区规则文件识别 maxReadOnlyEntries: 200 maxGrants: 8 allowWritableRequests: true # 置 false 即不许模型申请可写路径 watchProtectedPaths: true # 置 false 即不监听工作区变化, 只按时间兜底 watchTtlMinMs: 2000 watchTtlMaxMs: 30000

readOnlyPaths 的每一项是一行 gitignore 语义的模式,数组逐行合并为生效文本:

- 不含 / 的条目(如 .git、vendor)在工作区内任意层级匹配,覆盖嵌套仓库等场景。
- 以 / 开头或含中间 / 的条目锚定到工作区根(如 /.git、dist/a.txt);字面条目即使尚不存在也保留保护,例如 git init 之前的 /.git。
- // 开头的条目是文件系统绝对路径(如 //etc/pki),这是本插件额外支持的写法,gitignore 没有这种形态。
- 行首 ~ / ~/... 展开为当前用户家目录,行内 $NAME / ${NAME} 展开为环境变量,与「额外可写根」同一套规则:展开出的绝对路径写成 // 形态,未设置或空值的变量整行丢弃并告警,\$ 保留字面 $,~user 不支持。因此 ~/.ssh 与 $HOME/.ssh 都等价于 //<家目录>/.ssh。
- 尾部 / 表示只匹配目录(如 build/)。
- 通配: 匹配单段内任意字符,? 匹配单字符,[...] 字符类(含 [:alpha:] 等 POSIX 类), 独立成段时递归(如 a//b);\ 转义下一字符(\#、\!,尾部空格用 \ 保留)。
- ! 开头剔除匹配项,按 gitignore 的 last-match-wins 顺序解释;受保护目录内部无法通过取反重新放行后代。
- write / edit 按模式逐路径判定,与路径是否存在无关;bash / pwsh 侧的保护路径是枚举出来的,只覆盖展开时刻已存在的路径。解析时会解开符号链接并去重。
- 置为空列表 [] 即停用保护(插件仍在,只是不再多挡任何路径)。

工作区只读规则文件(.readonly)的条目不参与 ~ 与环境变量展开:那份文件是工作区自带的,只认工作区内的相对条目(绝对条目与 .. 越界条目会被拒绝并告警)。需要家目录或环境变量的写法请放在插件页文本或 patch 配置里。

writablePaths 的每一项是一行字面路径,不是 gitignore glob:

- 行首 ~ 或 ~/... 展开为当前用户家目录;~other 不支持。
- $NAME 与 ${NAME} 展开为环境变量;未设置或空值的变量整行丢弃并告警。\$ 保留字面 $。
- 宿主绝对路径(/tmp/extra)或 // 前缀(//tmp/extra)按文件系统解析。
- 其余相对当前会话工作区,含 ..(如 ../sibling-project)。
- 工作区内的路径本来就可写,展开时忽略并告警;文件系统根(/ 或盘符根)拒绝,避免把只读宿主根整棵翻成可写。
- 不支持通配与 ! 取反。不存在的路径仍保留词法形态:write / edit 与 Seatbelt 可按前缀放行,bwrap / Landlock 在叠加时跳过并告警。
- 只在 workspace-write 下并进 allow-list,不打穿 read-only。保护路径优先:额外根内部仍可被保护。
- 置为空列表即不额外放行。

readDenyPaths 的每一项与 readOnlyPaths 同一套语义(gitignore 模式,含 // 绝对条目,~ 与环境变量展开,! 取反),但作用在读上:命中的路径 read / read_image / grep / glob / str_replace_editor(仅 view)会被拒绝,沙箱命令同样读不到。默认空列表,即默认不禁止任何读取。

- 会话授权同时解锁读:request_writable_path 批准过的路径连同其后代既可写也可读,工具侧与命令侧一致。
- read-only 下同样生效(read-only 限制的是写,读本来是全开的);danger-full-access 下不介入。
- 它不读工作区的只读规则文件:.readonly 那份文件只参与写保护。
- 用户在插件页保存过读拒绝文本后,这个数组不再生效(与另外两份文本同一套覆盖规则)。

hardenBroker 是 macOS broker 逃逸加固的部署 base,布尔值,缺省 true:

- 开启时在 Seatbelt profile 末尾追加 broker 拒绝形式(见「保护范围」)。
- 关掉后命令按官方 profile 运行,只影响 macOS,只影响这一个加固;保护路径与额外可写根照常。
- 用户在插件页拨动开关后该值不再生效。

hardenWsl 是 WSL 互操作逃逸加固的部署 base,布尔值,缺省 true:

- 开启且宿主是 WSL 时,在 bwrap profile 上叠加 tmpfs /mnt 与 /run/WSL 等(见「保护范围」)。
- 关掉后命令按官方 profile 运行,只影响 WSL,只影响这一个加固;保护路径与额外可写根照常。
- 非 WSL 宿主上即使开启也不改 argv。
- 用户在插件页拨动开关后该值不再生效。

readonlyFileName / maxReadOnlyEntries / maxGrants / allowWritableRequests / watchProtectedPaths / watchTtlMinMs / watchTtlMaxMs 同理,是只读规则文件、可写申请与命令侧刷新的部署 base(见后两节),用户保存过对应字段后该值不再生效。

源码分三块,边界是「有没有文件系统依赖」:

| 文件 | 职责 | 依赖 |
| src/gitignore.ts | gitignore 语义的解析与逐路径匹配(PatternSet.match),规则文件校验与可写申请的保护判定共用 | 纯字符串/正则,零运行时依赖 |
| src/patterns.ts | 把模式枚举成具体路径,供保护路径清单与命令沙箱使用 | node:fs、canonicalPath |
| src/fs.ts / src/policy.ts / src/provider.ts | 三个挂载点:write/edit 围栏、沙箱策略、进程沙箱 argv 叠加 | DSH 引擎 |
| src/read-fence.ts | 读拒绝的工具层围栏(tools/pre-execute 判定与 grep / glob 结果过滤) | DSH 引擎 |
src/readonly-file.ts 负责读取与缓存工作区只读规则文件,src/path-expand.ts 负责展开额外可写根的字面路径,src/request-writable-path.ts 是可写申请的授权表与 request_writable_path 工具,src/grants-route.ts 与 src/grant-notice.ts 分别是会话面板的 /api 路由与撤回通知,src/seatbelt.ts 与 src/wsl.ts 分别是 macOS broker 加固、Seatbelt 形式的生成与 WSL 互操作加固。

插件页配置

插件页里 dsh-write-protect 卡片上的配置表单,从上到下是:保护路径与额外可写根两个多行文本,读拒绝路径,工作区只读规则文件名与最多条目数,单会话可写授权上限,macOS broker 逃逸加固,WSL 互操作加固,模型申请可写路径,监听工作区变化,以及刷新下界与上界两个毫秒值。草稿只留在卡片里,点「保存」才写进 profile 的 patch 层并实时生效;预览面板按当前草稿展开,不必先保存:

保护路径 .git secrets/.pem !secrets/example.pem

额外可写根 ../shared-scratch ~/scratch $HOME/scratch /tmp/dsh-extra

读拒绝路径 secret ~/.ssh ~/.aws/** !secret/public.pem

- 保护路径:# 开头是注释,空行忽略;! 排除,按最后匹配生效;不能在仍受保护的目录内部重新放行后代。行首 ~ / ~/... 与行内 $NAME / ${NAME} 会展开(展开出的绝对路径按 // 形态参与匹配),未设置或空值的变量整行丢弃并出现在「未生效」里。通配与锚定语义同「配置」一节。Windows 上的绝对条目写作 //C:/Users/me/secret:gitignore 语义里 \ 是转义符,/ 才是分隔符。
- 额外可写根:每行一条字面路径,不要通配。~ / ~/... 为家目录,$NAME / ${NAME} 为环境变量;绝对路径按文件系统解析,相对路径(含 ..)相对当前会话工作区。Windows 上 \ 是分隔符而不是转义符,C:\Users\me\caches 与 ~\caches 都按字面解析;盘符相对路径(C:caches)的落点取决于进程当前目录,会被拒绝并出现在「未生效」里。
- 读拒绝路径:语法与保护路径完全一致(含 ~ 与环境变量展开,! 排除与 // 绝对条目),但作用在读上;留空即默认不禁止任何读取。读拒绝允许用 ! 在受保护区域之外单独放行某条路径,但同样不能在仍受读拒绝的目录内部重新放行后代。
- 三份文本都按当前会话的工作区根解析,每个会话各自生效。开关是全局的,与工作区无关。
- 预览按钮把当前草稿交给 Host 展开,不必先保存:列出生效的保护路径、读拒绝路径与额外可写根,工作区规则文件的条目,本会话已批准的可写授权,以及被忽略或拒绝的行。展开使用当前选中会话的 cwd(即被主视图持有的那条会话记录);没有选中会话时预览直接报错,不回退部署工作区根 —— 部署根是进程 cwd,可能是一棵极大的树,在那里同步展开会堵住 Host 事件循环。这一页只列不改:加临时可写根与撤回授权在会话区的「写入权限」tab 上(见下节)。

mode 与 workspaceRoot 是官方 policy 行字段的复述(patch 对整行配置做替换,必须带上),取值语义与 base bundle 一致。

只读规则文件

工作区根可以放一份与「保护路径」同语义的规则文件(默认 .readonly,可在插件页配置里改名或置空关闭),逐行追加在保护路径文本之后:

<工作区根>/.readonly secrets/ /vendor !vendor/public/**

- 只认工作区根这一份,每个会话各读一份;整段保护工作区写 或 /,根下第一层用 /;/ 与 . 匹配不到任何路径,等于什么都不保护。
- 只接受普通文件(符号链接拒绝);// 绝对条目与越出工作区的 .. 条目拒绝,超限条目丢弃,都只告警不生效。
- 这份文件本身是唯一的硬保护:在 read-only 与 workspace-write 下任何授权都不放行它,也没有 request_writable_path 可受理它,会话面板的手动添加同样拒绝它;要改只能换文件名或由你在 DSH 之外编辑。(整个 danger-full-access 模式下本插件不介入,见「边界与已知限制」。)

读拒绝路径

读拒绝让模型与沙箱命令都拿不到某些位置的内容,典型用途是把密钥、凭据、别的租户的数据挡在 agent 之外:

插件页 "读拒绝路径" secret ~/.ssh ~/.aws/* //etc/pki !secret/public.pem

- 判定与写保护同源:同一套 gitignore 语义的模式表,逐路径匹配(含前缀围栏:命中的目录连同其后代一起挡),不依赖任何扫盘结果,! 按最后匹配生效;行首 ~ 与行内 $NAME / ${NAME} 与保护路径一样会展开,因此 ~/.ssh 等价于 //<家目录>/.ssh。
- 工具侧走引擎的 tools/pre-execute 钩子:read / read_image / grep / glob 的路径参数与 str_replace_editor 的 view 命令命中即被拒绝,拒绝信息里写明命中的是哪条模式,并提示可以申请本会话授权。
- grep / glob 的结果还要再过滤一次:它们默认搜索整个会话工作区,请求的 scope 没命中并不等于结果里没有命中,因此落在读拒绝范围内的命中会被逐条丢掉;一次调用的命中若全部被丢掉,这条结果会变成一条说明性的错误。
- 命令侧:macOS 上追加 (deny file-read* ...)(字面条目走正则,含通配的走枚举,与写保护同一套两条通道);Linux bwrap 用 tmpfs 盖目录,--ro-bind /dev/null 盖文件。Windows 上只挡工具侧,与写保护的限制一致。
- 读拒绝比写保护更容易误伤(提示词、依赖、构建脚本常常要读同一棵子树),因此没有默认值,需要时显式声明。

模型申请可写路径

提示词会引导模型:任务要反复写同一片受保护区域时(一个目录里的多个文件,构建产物树,若干次写入都依赖的工作区外路径),调 request_writable_path 申请本会话的可写授权,参数是 path(字面路径,支持 ~ / $VAR / // / 相对含 ..)与 justification(给你看的一句话理由);单个文件照旧用 write / edit 工具,那次被拒就算了。需要读某个被读拒绝的位置时走同一个工具:一条授权同时放开那条路径的读与写,不额外引入第二个申请入口。

审批理由会写明在放开什么:工作区外的路径批准后成为本会话的额外可写根(bash 与 write / edit 都能写;Windows 上只到 write / edit),工作区内被保护路径则放开那棵子树 —— write / edit 与命令沙箱(macOS Seatbelt 的 allow,Linux bwrap 的 bind)都认这份授权。授权按路径生效且覆盖其下全部后代,所以批过父目录之后再写子目录、子文件不会再次弹窗。授权只在本会话内存里,不写 settings,条数上限 maxGrants(默认 8);关掉 allowWritableRequests 后工具的任何调用都被拒,提示词也不再引导。保护路径与规则文件条目都在可申请范围内,因此可以配成「整个工作区只读 + 逐个目录批准」。

批准过的授权可以在会话区撤回,撤回之后模型会收到一条通知(见下节)。

会话写入权限面板

会话区(与「对话」/「轨迹」并排)有一个「写入权限」tab,它只处理本会话内存里的授权,也就是 request_writable_path 批准过的那几条:

- 列出本会话当前的授权:路径 + 类型(保护旁路 / 额外可写根),以及当前用掉了几条(maxGrants 上限)。
- 每条后面一个「撤回」:删掉这条授权,那片路径立刻回到保护判定里 —— 后续的 write / edit 与命令沙箱都按没批过处理。已经跑起来的命令不受影响,命令侧的挂载与 profile 在启动时就定好了。
- 下面的输入框手动加临时可写根:支持 ~ / $VAR / .. / 相对会话工作区,落点在工作区内时记作保护旁路(放开命中它的保护条目),在工作区外时记作额外可写根。记录与模型申请批准后的完全同质:同样只在本会话内存里,同样受 maxGrants 限制,不写配置文件 —— 所谓「临时」就是这个意思,会话结束或重启 dsh 之后自动消失。
- 当前会话是 read-only 时面板会提示:这时加进来的授权要等会话切到 workspace-write 或 danger-full-access 之后才生效。

撤回之后会往该会话投一条模型可见的消息,说明用户撤掉的是哪条路径,那里已经重新受保护,需要时应当重新申请而不是硬写。提示词里那份授权清单是状态(撤回之后那一行自己就没了),这条消息补的是事件(谁撤的,撤的是哪条),两者不是重复:只看清单的话,模型可能正按旧快照继续往那里写,撞上拒绝之后开始瞎试。

投递走 agent.inject,也就是排进模型下一步而不唤醒空闲会话(不会因为一次撤回就替用户烧掉一轮 token);消息本身按 user/message 写进会话日志,因此 dsh 重启、同一会话恢复之后模型照样看得到。这条通知没有落盘记录,也不做跨会话的「撤回名单」:授权本来就是会话级的,新建会话没有历史授权可谈,需要长期不写的位置请写插件页配置或工作区规则文件。

面板本身是 Host 的 /api 路由(/api/dsh-write-protect.grants),调用者就是用户本人,因此不经过审批弹窗;路径解析与保护判定复用模型申请那一套,规则文件本身同样不接受授权。

保护范围

保护路径与额外可写根会同时作用在下面几个入口:

| 入口 | 哪些系统 | 效果 |
| write / edit 工具 | 全平台 | 按保护模式逐路径判定,命中即拒绝;workspace-write 下额外根放行;danger-full-access 下全部放行 |
| read / read_image / grep / glob / str_replace_editor(view) | 全平台 | 按读拒绝模式逐路径判定,命中即拒绝;grep / glob 的结果再逐条过滤;会话授权放行 |
| bash 等命令 | Linux, macOS | 内核级只读 / 额外可写;读拒绝用 deny file-read(macOS)或 tmpfs / /dev/null 覆盖(bwrap);macOS 上字面条目按正则拒绝,与路径当前是否存在无关;Windows 做不到,见下方限制 |
| 提示词 | 全平台 | 先告诉模型哪些不能写,哪些不能读,哪些额外根可写,需要反复写或确实要读时怎么申请;Windows 上另有一段说明命令侧不受这些条目约束 |
| 本会话授权(request_writable_path) | 全平台 | 工作区外路径按额外根生效;保护旁路与读放行对写入工具,读工具与命令沙箱(bwrap / Seatbelt)都生效 |
| 会话写入权限面板 | 全平台 | 列出并撤回本会话授权,手动加临时可写根;撤回后向会话投一条通知消息 |
| macOS broker 加固 | macOS | 堵住 open 经 launchd 把命令挪到沙箱外执行 |
| WSL 互操作加固 | WSL + bwrap | 堵住 Linux 侧 exec Windows PE 把命令挪到沙箱外执行 |
两类入口的判定方式不同,这是有意为之:write / edit 拿得到目标路径,所以直接按 gitignore 模式判定 —— 深层嵌套、尚未存在的匹配一样挡得住,每条写入只做几次正则;bash 的沙箱(mount / profile)只能吃具体路径,所以那一侧才需要枚举展开。读拒绝沿用同一套判定:读工具走引擎的 tools/pre-execute 钩子,按模式逐路径判定(那里拿得到会话,因此能认会话授权);命令侧则与其他写保护一样,靠枚举与正则。枚举走 fs.promises,每次 readdir / lstat 让出事件循环;同步的 resolve() 只注入模式原文和缓存里已有的路径,不在会话加载时扫盘。提示词同样只陈述模式,不枚举绝对路径。

读拒绝没有放在 ctx.fs 那一层,原因是读接口(readText / streamText / listDir / stat)的签名里既没有 policy 也没有会话,在那一层判定既拿不到会话工作区根,也认不了本会话授权;而 grep / glob 的 ripgrep 是 ctx.subprocess 直接拉起的,既不经过 ctx.fs 也不经过命令沙箱,只有工具派发层挡得住。

macOS 上命令侧还有第二条通道:Seatbelt profile 支持按正则匹配路径,因此字面条目会被直接翻译成 (deny file-write (regex ...)),不经过枚举 —— 展开当时还不存在的路径(会话中途 git init 出来的 .git)同样挡得住,也不受展开缓存窗口影响。读拒绝同样有这条通道,生成的是 (deny file-read* (regex ...))。含通配或转义的条目仍走枚举清单,原因见下方限制。

命令侧清单的保鲜

bwrap 只能挂载真实存在的目录,所以命令侧的保护清单来自一次「展开」,而展开结果会缓存。会话中途才出现的受保护路径(例如刚 git init 出来的 .git)若不在清单里,那条命令就写得进去。为此有两层保障:

- 正则拒绝 (macOS):字面条目不经过展开,见上一节。
- 监听 + 自适应刷新 (全平台):只给「正在运行 agent 的会话」的工作区根装递归监听;agent 跑完或会话销毁就摘掉,空闲不占资源。监听一报变化就立即在后台重算清单(短时间内的多次事件合并成一次),所以下一条命令直接用新清单,不必等缓存过期。
- 时间兜底:没有事件时,按「上次展开耗时 × 10」兜底重算,并夹在 watchTtlMinMs(默认 2 秒)与 watchTtlMaxMs(默认 30 秒)之间;监听装不上或漏事件时不会一直陈旧。关掉 watchProtectedPaths 后不装监听,只剩这一层。
- 命令遇到已过期的缓存时会等这次扫描完成再执行,因此刷新不会变成「先放行、事后补救」。

这一步只影响命令侧。write / edit 围栏始终按模式原文判定,与展开、监听、缓存都无关。

主场景是 workspace-write。read-only 下官方已挡住全部文件写入,额外可写根不打穿;但官方 profile 的 (allow default) 在两种模式下都一样,所以 broker 加固不区分模式,读拒绝在 read-only 下照常叠加(那个模式限制的是写)。read-only 下会话授权只以只读方式把路径重新露出来,不会打穿只读。WSL 互操作同样不区分模式:Windows 进程写的是真实磁盘,read-only 的 --ro-bind / / 一样挡不住。

macOS broker 逃逸加固

官方 macOS profile 是 (version 1) (allow default) (deny file-write) ...,mach-lookup 与 process-exec 全开。而经 launchd 代理启动的进程不继承 Seatbelt profile,于是沙箱内一条 open x.app 就能让启动的进程在沙箱外任意读写,deny file-write 被整条绕开 —— read-only 同样会被打穿。本插件在 profile 末尾追加:

(deny mach-lookup (global-name-prefix "com.apple.coreservices"))
(deny appleevent-send)
(deny mach-priv-task-port)

SBPL 按 last-match-wins 解释,追加在末尾才能盖过 (allow default)。com.apple.coreservices 是 LaunchServices 的服务名段,open / NSWorkspace 靠它把请求交给 launchd;名称过滤器按 reverse-DNS 分段匹配,所以只能整段拒绝,收窄到子服务无效。appleevent-send 关掉让别的 app 代劳那条路,mach-priv-task-port 关掉注入已运行进程的 task port。

加固只做收紧,不放宽任何位置;常规命令(node, git, pnpm, python, curl, tar, rsync 等)不受影响。

插件页的「macOS broker 逃逸加固」开关与 patch 的 hardenBroker 控制这一个加固是否生效,缺省开启。关掉后 provider 原样返回官方 argv,适合确实需要从沙箱内驱动宿主 GUI 的场景;关掉即恢复可以被 open 打穿的状态。保护路径与额外可写根的叠加不受这个开关影响。

WSL 互操作逃逸加固

官方 Linux bwrap 是 --ro-bind / / 加工作区 --bind,没有 seccomp,也不拦 execve。WSL2 会把 Linux 侧启动的 Windows PE(典型路径在 /mnt/c)交给 Windows 宿主创建进程;那个进程不继承 bwrap 的挂载,可经 UNC 写回真实磁盘,于是保护路径的 --ro-bind 与 write / edit 围栏都被绕开 —— read-only 同样会被打穿。本插件在探测到 WSL 时,在官方 profile 之后叠加:

--tmpfs /mnt
--tmpfs /run/WSL
--tmpfs /proc/sys/fs/binfmt_misc
--bind /dev/null /init
后挂载覆盖早挂载。/mnt 藏掉 Windows 盘与 WSLg;/run/WSL 藏掉 interop 套接字,工作区里即便有 PE 也连不上 Windows;/init 是 WSL 的互操作翻译器。宿主上没有的路径会跳过(bwrap 的 --tmpfs 要求挂载点已存在),避免整条命令被拒。工作区本身若在 /mnt 下(Windows 盘上的项目),tmpfs 之后会把工作区重新 bind 回去;额外可写根若在 /mnt 下,同样在 tmpfs 之后重新露出来。

加固只做收紧,不放宽任何位置;常规 Linux 命令(node、git、pnpm、python、curl、tar、rsync 等)不受影响,只是沙箱内看不到 /mnt/c 这一类 Windows 盘。挂在 /mnt/wsl 下的东西(Docker Desktop 的跨发行版挂载一类)同样会看不见,需要在沙箱内用它们时关掉这个开关。

插件页的「WSL 互操作加固」开关与 patch 的 hardenWsl 控制这一个加固是否生效,缺省开启。关掉后 provider 不叠加上述参数,适合确实需要从沙箱内读 Windows 盘或跑 Windows 程序的场景;关掉即恢复可被互操作打穿的状态。保护路径与额外可写根的叠加不受这个开关影响。非 WSL 宿主上即使开启也不改 argv。

patch 配置和插件页文本走同一套解析。

边界与已知限制

- Windows 上 bash 挡不住,也放不宽:write / edit 能挡保护路径、能放行额外根;bash / pwsh 两者都不行。Windows 沙箱只能把整个工作区设成可写或不可写。这条差别同时写进模型可见的系统提示词与 request_writable_path 的工具描述(只在 Windows 宿主上追加),免得模型把「命令跑通了」当成「模式没命中」。
- broker 加固只在 macOS 生效;WSL 互操作加固只在 WSL + bwrap 生效:官方 macOS profile 的 (allow default) 让 open 能把命令交给 launchd 在沙箱外跑,本插件追加的拒绝形式堵住这条路。WSL 上对应的代理通道是 PE 互操作,由 hardenWsl 堵住。两边都只收紧不放宽。Linux 的网络命名空间仍未隔离,沙箱内仍可连宿主守护进程(Docker socket、ssh-agent 一类)让外面代劳,这类问题本插件不处理。
- 工作区或额外可写根是整块 Windows 盘时:tmpfs /mnt 之后会把那条路径重新 bind 回去,PE 文件重新可见,但仍没有 /run/WSL 套接字。真正要在沙箱内用 Windows 程序,请关掉 hardenWsl。
- hardenWsl 会连带藏掉 /mnt/wsl:WSLg 与 Docker Desktop 的跨发行版挂载都在那里,沙箱内会看不见。
- Linux 没有 bwrap,落到 Landlock 时:没法单独保护子路径,也没法藏 WSL 互操作入口,命令按官方沙箱跑并告警一次;额外可写根可以加 --rw。write / edit 两者都生效。
- 完全放开沙箱时(danger-full-access):本插件整体不介入 —— bash 不进沙箱,write / edit 的保护路径与规则文件判定也跳过。该模式是用户显式选择的「不设限」,保护只在 read-only 与 workspace-write 下生效。
- Linux bwrap 要求路径真实存在:通配扫出来的保护路径如果当时还不在磁盘上,会跳过这条只读挂载并告警。需要无条件保护的工作区根路径请用字面条目(如 /.git);字面条目即使还不存在,write / edit 也会拒绝。
- 命令侧的枚举窗口(macOS 字面条目除外):Linux bwrap / Landlock 与 macOS 上的通配条目只能按枚举出来的路径保护。有 watcher 时,变化之后的下一条命令就会吃到新清单;watcher 不可用时退回时间兜底(最多 watchTtlMaxMs,默认 30 秒)。macOS 上的字面条目没有这个窗口(按正则拒绝,见上节),因此 git init 也建不出 .git。已经要保护的目录不会再往里扫,里面的匹配项不再单独列出;被 ! 放行的目录还会继续找。目录符号链接不跟随,避免扫到工作区外。write / edit 不受这条限制:它直接按模式判定。
- 正则拒绝只覆盖字面条目,且遇到 ! 就整体放弃:含 ? [ 或 \ 的条目仍走枚举清单;文本里只要出现 ! 取反,命令侧就退回纯枚举(纯 deny 表达不了 last-match-wins)。目录标记条目在正则通道上不区分目标是文件还是目录,同名文件也会被一并挡住,属于收紧。
- 尾部 / 按那个目录本身保护:和保护其下全部后代等价,同时避免枚举全部后代,代价是该目录自己也写不了。
- 规则文件的条目同样受缓存窗口影响:规则文件内容按 1 秒 TTL 重读,改完最迟 1 秒后按新内容判定;新增的匹配路径要等下一次枚举(监听报变化后立即重算,没有事件时最迟 watchTtlMaxMs,默认 30 秒)才会进命令沙箱的清单,write / edit 侧立刻按新条目判定。
- 授权是会话级内存态:request_writable_path 得到的授权,以及会话面板里手动加的临时可写根,都不跨会话,也不写配置文件;撤回只是删掉这条内存记录,不产生任何持久状态。需要长期生效的额外可写根请写插件页配置或 patch。
- 撤回通知依赖该会话有活着的 agent:投递对象是会话的 agent runtime;该会话当前没有活动 agent 时授权照常撤回,但通知投不出去,面板会把这件事如实写出来(不假装模型已经知道)。
- 授权在 bwrap 上要求路径真实存在:命令侧要把授权子树 bind 回可写,bwrap 要求挂载源存在;还不存在的授权路径在命令侧要等它被创建出来(write / edit 与 Seatbelt 不受这条限制)。
- Landlock 上没有命令侧的授权:纯 allow-list 无法表达「父目录只读,其中一棵子树可写」,强行加 --rw 会连上方一起放开,因此这种 runner 下授权只作用于 write / edit(告警一次)。
- Landlock 上也没有命令侧的读拒绝:同一个 allow-list 结构在放行的父目录里挖不掉一棵子树,因此读拒绝在这个 runner 上只作用于读工具,命令仍读得到,会告警一次。它同时挡住读拒绝的授权那一路(读本来就没被挡)。
- Windows 上读拒绝只挡工具侧:官方 Windows 沙箱只能把整个工作区设成可写或不可写,没有子路径的读拒绝形态(与写保护的限制同源)。
- 读拒绝的枚举窗口与写保护相同:bwrap 的 tmpfs 与 Seatbelt 的通配条目都按枚举出来的路径生效,新出现的匹配路径要等下一次展开(watcher 立即重算,没有事件时最迟 watchTtlMaxMs)。macOS 上的字面条目同样走正则通道,没有这个窗口。
- 读拒绝拦不住别的东西读:它只管本插件覆盖的那几条通道(读工具与 bash / pwsh 沙箱)。别的插件或工具自己开文件读(MCP 工具,浏览器,远端会话一类)不在这条围栏里。
- 读拒绝挡住的是内容,也顺带挡住执行:macOS 上 (deny file-read) 会连 exec 一起挡住(执行也要求读),这是收紧。
- 指向保护目录内部的符号链接会被拒绝,指向外部的不受影响。

本地开发

just install # 安装依赖 just typecheck # TypeScript 类型检查 just build # 构建 lib/ just test # 测试套件 (Seatbelt e2e 仅 macOS; bwrap e2e 仅 Linux; WSL 加固 e2e 仅 WSL) just verify # 以上全流程 + 打包预览

源码分三块,边界是「有没有文件系统依赖」:

| 文件 | 职责 | 依赖 |
| src/gitignore.ts | gitignore 语义的解析、编译与逐路径匹配(含 PatternSet.match),write / edit 围栏与可写申请的保护判定共用 | 纯字符串/正则,零运行时依赖 |
| src/patterns.ts | 把模式异步枚举成具体路径,供命令沙箱与预览面板使用(另有额外可写根的字面路径解析) | node:fs/promises、canonicalPath |
| src/fs.ts / src/policy.ts / src/provider.ts | 三个挂载点:write/edit 围栏、沙箱 policy(含规则文件,读拒绝与授权)、进程沙箱 argv 叠加 | DSH 引擎 |
| src/read-fence.ts | 读拒绝的工具层围栏:派发前按会话 policy 判定,结果侧过滤 grep / glob 的命中 | DSH 引擎 |

src/readonly-file.ts 读取并校验工作区只读规则文件,src/request-writable-path.ts 是 request_writable_path 工具与会话授权表(含撤回),src/path-expand.ts 负责额外可写根的字面路径展开(~ / 环境变量 / 平台差异),src/grants-route.ts 是会话写入权限面板的 /api 路由,src/grant-notice.ts 负责撤回之后投给会话的那条通知,src/seatbelt.ts 与 src/wsl.ts 分别是 macOS broker 加固与 WSL 互操作加固的参数生成。

测试覆盖:纯匹配器语义(锚定,**,字符类,取反,前缀围栏,目录标记,大小写,工作区外不match;不需要任何临时目录),路径解析语义(相对锚定,解开符号链接,去重,通配枚举与取反,额外可写字面路径),只读规则文件的解析与校验(符号链接拒绝,绝对与越界条目,条目上限,缓存与重读),可写申请的判定矩阵(直通,规则文件硬保护,工作区内旁路,超限,四种未获同意的结果)与授权落到 policy 的通道,会话面板路由的三个动作(清单,手动添加的内外分类与拒绝,撤回与通知投递),撤回通知消息的来源标记,面板数据面的请求体与失败解读,write / edit 按模式判定(启动后才出现的深层路径、尾部 / 与同名文件、工作区边界),读拒绝的判定与结果过滤(路径参数的相对解析,授权放行,danger-full-access 不介入,str_replace_editor 只挡 view,grep / glob 命中的逐条过滤与全挡时的错误反馈),读拒绝文本与枚举清单落到 policy 的通道(与写保护清单各自独立),bwrap / Seatbelt / Landlock 的命令行叠加(含 WSL 加固的插入位置与读拒绝的挂载顺序),write / edit 工具的拒绝与额外根放行矩阵,配置通道的 base 与用户覆盖分层,client bundle 的 loader 注册,macOS 上真实 sandbox-exec 的内核级端到端(包括 open broker 逃逸的对照组与加固后的拦截验证),以及 Linux 上真实 bwrap 的内核级端到端(保护路径写入 EROFS,读取照常,额外可写根可写;WSL 上再断言 Windows 盘与 interop 套接字被藏掉,关掉开关后盘仍可见;本机 bwrap 不可用时整组跳过)。

License

MIT
预览分享卡片长什么样 ↓azazo1/dsh-write-protect
分享 · DeepSeek Harness Hub
azazo1/dsh-write-protect

防止模型改写工作区里的指定路径. 同时支持放开某些外部目录的写入而不必放开沙箱.

这是你的仓库?认领条目

回到顶部