Overview
dsh-write-protect
README / EN
Package documentation
Registry summary
dsh.pub verifies the pinned bundle contract, runtime facts, and distribution semantics. The complete README remains in the source repository.
Read the full README on GitHubLIMITATIONS
Known limitations
- **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` 一起挡住 (执行也要求读), 这是收紧. - 指向保护目录内部的符号链接会被拒绝, 指向外部的不受影响.
