dsh-host-fs
Verifieddsh-host-fs · v1.0.2 · MIT · Web UI
Delete and move files from the DSH host process — including paths outside the workspace, where DSH's own fs tools have no delete and the sandboxed shell cannot write. Gated by a session allow/deny list and a path allow/deny list. · 在宿主进程里删除/移动文件,补上 DSH 文件
Install
dsh plugin add dsh-host-fs Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-host-fs
装上就能用(三步)
- 装:
dsh plugin --profile <你的 profile> add dsh-host-fs(或github:sniperdown/dsh-host-fs),然后重启一次 DSH。 本插件在 DSH 0.2.0-rc.2 上实测通过。更老的宿主没有settings.plugins.tab槽位, 设置页不会出现(fs_remove/fs_move仍可用,但只能靠cordis.patch.yml配)。 插件必须重启才会加载;改完配置也要重启或让它重载。 - 配:设置 → 插件 → 顶部 「文件权限」 页。
- 会话权限选
白名单,在列表里勾上允许使用它的会话(每行是「标题(会话 id)」,按最后活动时间排序) - 路径白名单写允许删除/移动的目录(含所有子路径);路径黑名单留着挡不该碰的东西
- 点 保存(一次写入,勾选本身不会立刻落盘)
- 会话权限选
- 用:那个会话里就能调
fs_remove/fs_move了。拿不准就先dryRun: true让它报账。
默认状态是全关:sessionMode = off、路径白名单为空 —— 装上什么也做不了,必须先显式放行。
不知道会话 id 时,在目标会话里敲 /fs-acl,它会打印可直接复制的 id 和当前生效的配置。
⚠️ 手改 profile 的
cordis.patch.yml时,这个插件的条目只能是id+config的覆盖行。 不要再写name、也不要放进insert:—— 那会和 bundle 自带的声明重 id,市场更新时报duplicate loader entry id并把别的插件一起回滚。
在 DSH 宿主进程里删除 / 移动文件 —— 包括工作区之外的路径。
为什么需要它
DSH 有两条碰文件系统的正规通道,而在 Windows 上都不能删工作区外的文件:
| 通道 | 限制 |
|---|---|
ctx.fs / read·write·edit 工具 |
只有 stat / read / list / write / edit —— 根本没有删除 |
shell 工具(pwsh / bash) |
子进程跑在 Low 完整性级别,写不了/删不了任何 Medium 标签的目录(比如 DSH 自己的 home) |
插件的工具跑在宿主进程里,权限就是用户本人的权限 —— 这就是全部机关:fs_remove / fs_move 由宿主执行普通的 node:fs 调用,所以 shell 做不到的地方它们能做。
安装
# 从 npm(发布后)
dsh plugin --profile <name> add dsh-host-fs
# 从 git 仓库
dsh plugin --profile <name> add github:you/dsh-host-fs
# 从本地目录
dsh plugin --profile <name> add link:/absolute/path/to/dsh-host-fs
配置
设置 → 插件 → dsh-host-fs 有自动生成的表单;设置 → 插件 → 文件权限 是专用页:
四档会话权限、搜索框、按类别分组的会话勾选表(每行是 标题(会话 id))、
pathAllow / pathDeny 两个文本框,点一次「保存」才写入 profile —— 勾一下不会立刻存。
sessionList 由宿主在保存时重算:界面只负责它看得见的那批会话的勾选;
通配(qq-*)、preset:example、*,以及已经不在列表里的旧会话 id 都原样保留。
两层独立的闸门,默认都在安全侧。
| 配置项 | 类型 | 默认 | 含义 |
|---|---|---|---|
sessionMode |
off · allowlist · denylist · all |
off |
谁可以调用。 off = 谁都不行。 |
sessionList |
文本 | 空 | 一行一条(换行 / 逗号 / 分号都认)。写法:精确 id(session-…、qq-group-…)、前缀通配(qq-*)、按人格预设(preset:example)、全部(*)。 |
pathAllow |
文本 | 空 | 一行一条路径。写进一条 ⇒ 该路径本身以及它下面所有子路径都允许。空 = 什么都不允许。 |
pathDeny |
文本 | 空 | 语法同白名单,优先级高于白名单。 |
askOutside |
布尔 | true |
两边都没命中的路径:在 GUI 里问一次(见下)。 |
路径判定(精确版)
1. 是驱动器根(C:\ 、H:\ …) → 拒绝(不可配置)
2. 落在某条 pathDeny 之下 → 拒绝(压过白名单)
3. 落在某条 pathAllow 之下 → 允许
4. 其余 → askOutside ? 弹一次审批 : 拒绝
于是 pathAllow = C:\1、pathDeny = C:\1\2 时:
| 路径 | 结果 |
|---|---|
C:\1 |
允许 |
C:\1\3、C:\1\3\deep\x.txt |
允许 |
C:\1\2、C:\1\2\3 |
拒绝(黑名单优先) |
C:\1\20 |
允许(不在 C:\1\2 之下) |
fs_move 的源和目标各走一遍同一套判定 —— 所以把东西从允许目录移进禁止目录会被拒。
一次性审批
askOutside 开着、且路径两个名单都没命中时,插件会调用 DSH 的审批 API(就是那个"需要完整权限"的框)。前提是该会话的审批档位是 ask;只接受 allowed-once 一种结果,所以它永远不可能放宽配置。没有应答者时(QQ 会话、headless)一律 fail closed。
这里故意没有 unsafe / force 之类让模型绕过名单的参数 —— 配置说了算。
工具
fs_remove
| 参数 | 类型 | 含义 |
|---|---|---|
path |
string,必填 | 绝对路径;相对路径以会话工作目录为基准。 |
recursive |
boolean | 删除非空目录时必须显式给 true。 |
dryRun |
boolean | 只统计、不删。 |
返回 { path, kind, entries, bytes, dryRun, approved },kind 取 file · dir · link · other · missing。路径不存在不是错误 —— 返回 kind: "missing",所以重试是无害的。
fs_move
| 参数 | 类型 | 含义 |
|---|---|---|
from |
string,必填 | 源路径。 |
to |
string,必填 | 目标路径。 |
overwrite |
boolean | 允许覆盖已存在的目标。 |
dryRun |
boolean | 只报告,不移动。 |
会自动创建目标缺失的父目录;拒绝把目录移进它自己的子树;跨盘移动会明确报错(请先复制再 fs_remove)。
/fs-acl
只读斜杠命令。打印当前会话 id、preset、cwd、当前加载的配置,以及"要放行本会话就抄这一行":
dsh-host-fs · 当前会话
会话 id : session-example-01-…
preset : (默认)
会话闸门 : sessionMode=allowlist → 拒绝(not-in-allowlist)
名单 : (空)
...
sessionList 里放这一行即可放行本会话:
session-example-01-…
它还会提醒:报告里的值是内存中正在跑的那份配置 —— 如果你刚改过设置而这里没变,说明插件还没重载。
Windows 相关的两条实测结论
- junction 不是
lstat意义上的符号链接:isSymbolicLink()为false、isDirectory()为true,readlink抛EINVAL。所以判定用列父目录时的Dirent.isSymbolicLink(),拿不到再退回realpath比较。 fs.rmSync(recursive)不会删穿 junction:实测删掉"装着 junction 的目录"之后,目标及其内容完好无损。junction 路径本身会报kind: "link",只删链接。
只读文件会清一次属性后重试一次;其余错误原样抛出。
已知限制(v0.3)
- 会话闸门是调用时拒绝,不是按会话隐藏工具(被拒的会话仍看得到工具,但会收到一句明确的"怎么放行")。
- 旧版本的会话没有标题事件,列表里显示
(未命名);会话 id 旁边有「在线」标记。 - 设置页是插件的一部分:装好/更新后要重启宿主,页面才会出现在「设置 → 插件」里。
- 会话标题是一次批量读的(几百个会话可能要等一两秒);读标题超时不影响勾选与保存。
- 界面只显示调用插件自己路由的权限页;配置写入走 DSH 的
configEditor,不手写 profile patch。
许可证
MIT