Turns DSH permissions into 9 selectable presets (3 sandbox modes x 4 approval tiers), with rule plus LLM risk classification across four risk levels, password-gated approval for high-risk operations, and a JSONL audit log.
Install
# from a prebuilt release tarball
dsh plugin --profile web add "https://github.com/zhang8019/dsh-permission-matrix/releases/latest/download/dsh-permission-matrix.tgz"
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:zhang8019/dsh-permission-matrix
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
把 DSH 的权限从「几个固定档位」拆成两个独立维度并重新组合:
- 沙箱(读写边界):工作区只读 / 工作区读写 / 全电脑读写
- 审批(操作批准方式):人工审批 / 自动同意 / 自动风险审批 / 自动拒绝
两者组合出 9 个可切换的执行类型,外加全局默认预设与LLM 机器人默认预设两个维度——社交渠道(QQ / 微信 / 企微 / Telegram…)的会话可以用与 Web 会话不同的权限档位。
取代
dsh-auto-classifier的单点auto预设:本插件保留它的核心思路(规则 + LLM 裁判 + 硬拒绝),但把预设表拆成完整的组合矩阵。
9 个执行类型
| # | 预设 id | 显示名 | 沙箱 | 审批 | 行为 |
|---|---|---|---|---|---|
| 1 | ro-human |
工作区只读权限 + 人工审批 | read-only | ask | 一切写入被沙箱拒绝,越权操作弹人工确认 |
| 2 | ro-deny |
工作区只读权限 + 自动拒绝 | read-only | never | 写入一律拒绝,无人审批环节(真正只读) |
| 3 | ww-human |
工作区读写权限 + 人工审批 | workspace-write | ask | 工作区内可写;越界操作弹人工确认(默认) |
| 4 | ww-classify |
工作区读写权限 + 自动风险审批 | workspace-write | ask | 工作区内可写;越界与高风险操作按风险分级裁决 |
| 5 | ww-deny |
工作区读写权限 + 自动拒绝 | workspace-write | never | 工作区内可写;越界操作一律拒绝 |
| 6 | fa-human |
全电脑读写权限 + 人工审批 | danger-full-access | ask | 文件全放行;需批准的操作交给人工 |
| 7 | fa-deny |
全电脑读写权限 + 自动拒绝 | danger-full-access | never | 文件全放行;需批准的操作一律拒绝 |
| 8 | fa-auto |
全电脑读写权限 + 自动同意 | danger-full-access | ask | 文件全放行;需批准的操作自动放行(硬拒绝保护) |
| 9 | fa-classify |
全电脑读写权限 + 自动风险审批 | danger-full-access | ask | 文件全放行;操作按风险分级裁决 |
为什么是 9 个而不是 12 个
3 沙箱 × 4 审批 = 12,但有三组功能重复:
- 自动同意在三种沙箱下等效:
只读 + 自动同意里写操作被沙箱拒 → 自动批准 → 照样写成功,最终权限与全电脑 + 自动同意相同,只是每次多一次往返。故只保留一个。 - 自动风险审批在只读下与工作区读写下等效:分类器对工作区内写入确定性放行,两者结果一致。故只保留工作区读写档。
关键设计:审批档只有两个合法值
@deepseek-ai/dsh-user-approval 的 APPROVAL_POLICIES 只有 ask / never。因此:
| 审批档 | 预设表里写的值 | 实际由谁实现 |
|---|---|---|
| 人工审批 | ask |
浏览器应答者(插件不干预) |
| 自动同意 | ask |
本插件在 approval/request 返回 allowed-once |
| 自动风险审批 | ask |
本插件在 tools/pre-execute + approval/request 分级裁决 |
| 自动拒绝 | never |
DSH 原生确定性拒绝(插件不干预) |
插件通过 takeover 表按当前会话的预设 id 分流,未列出的预设全程 next(),行为与 DSH 原生一致。
风险分级与判定顺序
- 硬拒绝(HARD) → 默认直接拒绝,人工也不能批准(
riskPolicies.hard = 'deny');配为ask/password时改为密码批准通道(见下节),但不提供「放行」选项; - 高风险(HIGH) → 按
riskPolicies.high分派;非盘根递归/批量删除、普通git push走此档,工作区内路径豁免; - 工作区内结构放行(write/edit/read 且目标在会话工作区内)→ 放行(受保护目标除外:
~/.dsh、凭据、.git/config|hooks);只读工具读取系统目录/配置同样放行——读不是写; - 允许规则(常规 git、包管理器查询、语言运行时、PowerShell cmdlet 等)→ 放行;
- LLM 裁判(可开关)→ 语义裁决;
- 四档风险策略(低 / 中 / 高 / 极高各自可选):
| 风险级别 | 来源 | 默认策略 | 可选值 |
|---|---|---|---|
| 低 | 允许规则命中 / 工作区内操作 / 裁判判定安全 | allow |
allow / deny / ask / password |
| 中 | 规则未命中 / 裁判不确定 | deny |
allow / deny / ask / password |
| 高 | 裁判判定危险 / 非盘根递归删除 / git push |
deny |
allow / deny / ask / password |
| 极高 | 硬拒绝清单(24 条) | deny |
deny / ask / password(不提供放行) |
ask(转人工)在完全权限下同样可用,经tools/pre-execute返回{kind:'ask'}→ approval seam(浏览器审批弹窗,点同意即放行);password(密码批准)= 挂起等密码,由本插件自己的密码弹窗接管(见下节),与模型行为无关。
执行面识别(避免"提及即拦截")
HARD/HIGH 规则只匹配真正会被执行的片段:executionSurface() 会剥离注释、字符串字面量、变量赋值右侧与文件名。因此 $m = "shutdown /s"(仅赋值)、# shutdown /s(注释)、Test-Path pm-sudo-notes.txt(路径含关键词)、Write-Host "..."(输出文本)都不会再被误拦;真实执行 shutdown /s 依旧拒绝。
同理,只读形态与写入形态被区分开:schtasks /query、net user(列用户)、icacls <路径>(查 ACL)、bcdedit /enum、diskpart /? 等只读查询与帮助文本放行,对应的写/变更形态仍然拒绝。
「密码批准」——与放行 / 拒绝 / 转人工并列的第四种方式
低 / 中 / 高 / 极高四档都可以选 password:该档操作会被挂起(不失败),由本插件要求输入批准密码;输对了当场执行,输错或超时按拒绝处理。
- 挂起时登记一条待批准请求(工具名 + 投影目标 + 指纹 + 风险档,审计
outcome=password-required); - 输入入口有三处(等价):
- 密码弹窗:注册进
shell.overlay槽位,浮在整个应用之上——出现即输入密码点「批准并立即放行」,被挂起的那次调用当场执行,不需要模型重试;点「忽略」按拒绝处理; - 设置 → 权限矩阵 → 批准密码:逐条「批准(放行一次)」,或「授权下一次需密码的操作」(通配);
- 浏览器打开
<Web 地址>/dsh-permission-matrix/approve(纯表单页,无需前端脚本);
- 密码弹窗:注册进
- 超时(
approvalPasswordTtlMs,默认 300 秒)未批准 → 该次调用按拒绝处理,但请求仍留在列表里:此时批准会签一张绑定该指纹的一次性令牌,模型重试即放行; - 令牌/交付都只用一次:指纹不同、会话不同或过期均不放行。
password与ask的区别:ask(转人工)交给浏览器审批弹窗,点一下「同意」就放行;password必须知道口令,所以不会被"多点几次同意"绕过。极高风险档的ask与password等价(都走密码批准)。极高风险默认仍为
deny(直接拒绝);设置页对极高风险档只提供「拒绝」与「需批准密码」,配置allow会在加载期 fail-closed 拒绝。 未设置批准密码 ⇒ 所有选了「需批准密码」的档一律拒绝(弹窗也会明示"无法放行")。
密码学:口令以 scrypt(N=16384, r=8, p=1, 随机盐)哈希存储,明文既不落盘也不回传浏览器(/status 只回一个 approvalPasswordSet 布尔);approvalPasswordHash 不在设置页的可写键白名单里,只能经专用动作路由写入。待批准请求与令牌都在内存中,DSH 重启即失效。
硬拒绝清单在设置页只读,不可编辑——防止通过配置绕过硬边界。
全局默认 + LLM 机器人默认
| 选择器 | 作用域 | 存储位置 |
|---|---|---|
| 全局默认预设 | 新建的 Web / 常规会话 | DSH 原生 permission 命名空间的 defaultPreset(与 Settings → 权限 同源) |
| LLM 机器人默认预设 | 社交渠道会话(按工作区匹配) | 本插件的 permission-matrix 命名空间 |
机器人识别不感知任何具体机器人插件:只按会话工作区(cwd)匹配 robotWorkspaces 列表。机器人插件卸载后本插件照常运行,只是不再产生落在这些工作区的新会话。
安装
dsh plugin --profile <profile> add dsh-permission-matrix
本插件的 cordis.patch.yml 会:
- 把
permission行 restate 为上述 9 个预设; - 插入
permission-matrix插件行。
若同时装有
dsh-auto-classifier,请把它停用(两者争抢同一个permission行):# <profile>/cordis.patch.yml - id: auto-classifier name: dsh-auto-classifier disabled: true config: { enabled: false, presetName: auto }注意 patch 条目必须同时带
name字段,否则disabled不生效。
配置
设置 → 权限矩阵 页面(或 <profile>/cordis.patch.yml 的 permission-matrix 行):
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
takeover |
见上表 | 预设 id → 审批档(auto-allow / classify) |
riskPolicies |
{low:allow, medium:deny, high:deny, hard:deny} |
四档风险策略;极高风险只接受 deny / ask |
approvalPasswordHash |
空 | 批准密码的 scrypt 哈希(设置页写入,明文不落盘;不在可写白名单,仅专用路由可写) |
approvalPasswordTtlMs |
300000 |
密码批准窗口(毫秒):挂起等待时长 / 令牌有效期 |
hardApprovalPasswordHash / hardApprovalTtlMs |
— | 已废弃(v0.3.0 旧键):仅兼容读取,写入一律用新键;已设口令不会因改名丢失 |
llmJudge |
true |
LLM 裁判开关 |
judgeProvider / judgeModel |
空 | 留空 = 跟随当前会话模型 |
judgeStages |
both |
both / fast / thinking |
autoAllowHardGuard |
true |
自动同意档的硬拒绝保护 |
robotDefaultPreset |
fa-auto |
机器人会话默认预设 |
robotWorkspaces |
[] |
机器人会话工作区(绝对路径,含子目录) |
gitSnapshot |
true |
放行前 git 快照 |
gitSnapshotIntervalMs |
30000 |
快照节流 |
auditLog |
true |
审计开关 |
auditFile |
~/.dsh/permission-matrix/audit.jsonl |
审计文件 |
配置非法(未知预设 id、非法档位、极高风险档配 allow)会在加载期响亮失败,不会静默降级。
审计
每次裁决写一行 JSONL:
{"time":"2026-09-09T10:52:00.000Z","sessionId":"…","preset":"ww-classify","tool":"write","target":"D:\\out\\a.txt","risk":"medium","rule":"mid-policy:deny","source":"mid","decision":"deny","outcome":"blocked"}
outcome 取值含 passed / blocked / allowed-once / escalated-to-human / password-required(挂起等密码) / password-approved(密码批准后放行) / password-timeout / password-declined / password-cancelled。
只写文件与进程日志,不进入模型 transcript。
架构
src/
├── index.js 装配 + 三个钩子(tools/pre-execute、approval/request、session/created)+ 密码批准闸门
├── presets.js 9 预设的单一真源
├── preset-router.js 当前会话预设 → 是否接管、以哪档接管
├── decide.js 决策核心(纯函数:HARD → HIGH → 区内放行 → 允许规则 → null)
├── rules.js 硬拒绝 / 高风险 / 允许规则表 + 执行面提取 + 字段投影
├── password-approval.js 「密码批准」通道(scrypt 哈希 + 待批准请求 + 挂起等待 + 一次性令牌)
├── judge.js LLM 裁判(两阶段 + 跟随会话模型 + 失败降级)
├── snapshot.js Git 快照
├── audit.js JSONL 审计
├── robot-presets.js 机器人工作区 → 默认预设
├── settings.js settings 命名空间 + webServer 同源路由(含批准页)
└── client/index.js 设置页 + 极高风险密码弹窗(手写 __ModuleLoader__ bundle,注册 settings.section 与 shell.overlay)
测试
node --test tests/decide.test.js tests/password-approval.test.js tests/hooks.test.js
69 条用例,覆盖:硬拒绝(删根目录 / 格式化 / 提权 / 强推 / git reset --hard / 系统级包安装)、高风险分派(非盘根递归删除 / git push / 动态执行变量)、工作区内递归删除豁免、受保护目标(路径类 + 命令类)、拼接执行绕过(拆片段 / iex 动态执行 → 硬拒绝;纯输出文案不误伤)、误拦回归(只读查询 / 注释与字符串里的关键词 / _rsa / 只读读系统目录 / 读 settings.yaml)、四档风险策略(含 password 值)、密码批准(哈希校验 / 挂起与超时 / dismiss 与 abort / 一次性令牌 / 指纹与会话绑定 / fail-closed / 「弹窗输密码 → 挂起调用立即放行」端到端 / 中风险档同样生效)、fail-closed 校验、防回环、机器人预设切换、总开关。
实测与修复记录(v0.2.0,2026-09-10)
在 fa-classify(全电脑读写)与 ww-classify(工作区读写)两档预设下做了真实工具调用实测(400+ 条审计记录),完整报告见 docs/test-audit-2026-09-09.zh.md。首轮实测暴露的问题已在 v0.2.0 全部修复:
✅ 已修复的漏拦截(应拒却放行)
git reset --hard完全无规则 → 新增 HARD 规则hard:git-reset-hard;- 系统级包安装(
apt-get/pip/winget install)无规则 → 新增 HARD 规则hard:pkg-install-system; - cmd 风格递归删除绕过(
rd/rmdir/del /s /q不匹配 PowerShell 风格正则,实测能真删目录) → 新增 HARD 规则hard:rm-root-win-cmd+ 高风险规则; - 非盘根递归删除被放行 → 新增高风险(HIGH)档,按
riskPolicies.high转人工,工作区内路径豁免; - 命令类工具写
.dsh绕过保护 → 新增硬拒绝分支hard:protected-target; - 普通
git push被放行 → 新增高风险规则high:git-push(--force变体仍为 HARD); rm -rf ~/子目录不命中 → 高风险规则high:rm-recursive-unix覆盖。
✅ 已修复的误拦截(应放行却被拒)
- 只读查询与帮助文本误伤(
schtasks /query、net user、icacls <路径>、shutdown /?、Get-Help Stop-Computer、bcdedit /enum、diskpart /?) → HARD 正则收窄为写/变更形态; - 命令文本/注释/文件名/字符串里的关键词误伤(
shutdown、sudo) → 执行面识别executionSurface(); _rsa宽泛后缀误伤(my_rsa.txt) → 凭据规则收窄为明确的私钥/凭据路径;read工具读系统目录被拦(pwsh 读同一文件却放行) → 新增只读工具豁免(READ_PATH_TOOLS);- 读取
settings.yaml被当"篡改"拒 → 只读形态豁免,写入仍硬拒绝。
✅ 拼接/动态执行绕过(2026-09-10 修复)
- 原
已知限制所列的命令文本拼接绕过($p1='Remove-'; $p2='Item -Recurse -Force C:\'; & $p1$p2)已修复:实测还有更多同族漏拦($c='diskpart'; & $c、$x='shutdown /s'; iex $x、$cmd='net user hacker P@ss /add'; Invoke-Expression $cmd),一并堵上。 - 做法:检测到动态执行形态(
& $var/. $var/iex $var/$a$b相邻变量)时,把引号内的字符串当真实命令再跑一遍 HARD 规则,并识别被拆开的 cmdlet 动词片段('Remove-'这类「动词 + 连字符结尾」,记为hard:concat-fragment); - 关键取舍:该检查只在出现动态执行形态时触发,所以
Write-Host 'rm -rf /'这类纯文案不会被牵连——顺带修掉了此前"Write-Host里提到危险词就被判 hard"的误拦(纯输出 cmdlet 现在一律按执行面匹配); - 单独出现
& $someExe --version这种正常动态调用不硬拒绝,落到高风险档high:dyn-call(默认 deny,可配ask/password); - 残余风险:变量经多层间接(数组/哈希表/子表达式
$(...)拼接、[scriptblock]::Create)仍可能绕过静态分析,依赖沙箱层与 LLM 裁判兜底。
其它实测结论
- ✅ HARD 清单本体可靠:全部硬拒绝规则按预期触发(含大小写 / 引号 / 嵌套变体);
- ww 与 fa 差异只在沙箱层:插件规则与 LLM 裁决逐项一致;ww 下工作区外写入由文件沙箱兜底拦截(插件放行但实际写不进),命令类写工作区外同样被沙箱拒;只读不受限;
- 沙箱升级通道放开后,规则漏洞仍可穿透执行——因此漏拦截修复在 ww 模式同样必要;
- v0.2.0 验证:单元测试 51 条全过;仓库
src/与已安装 profile 的src/SHA256 逐一一致(实机生效)。
v0.3.0(2026-09-10):双重确认 → 批准密码
失效根因(实测 + 审计证据):v0.2 的「极高风险双重人工确认」要求"模型在窗口内原样重发同一条命令"才能完成第 2 次放行。实际行为是:
- 第 1 次调用被拒后模型不会原样重发,而是改写命令(加
Write-Host、换变量、换工具)或改走别的路径 → 指纹(工具名 + 投影目标)随之变化,hard-confirm-2分支永不命中; - 审计日志
~/.dsh/permission-matrix/audit.jsonl1064 条记录里hard-confirm-1出现 3 次、hard-confirm-2出现 0 次——即极高风险操作事实上无法被人工放行; - 用户实测现象一致:只弹一次窗,第 1 次批准被立刻当成拒绝,之后不再有第 2 次。
v0.3.0 替换方案:极高风险不再依赖模型行为,改为批准密码——拦截后登记待批准请求,用户在设置页(或 /dsh-permission-matrix/approve 批准页)输入密码才签发一次性令牌放行;没有密码就永远拒绝。设置页新增「极高风险批准密码」区块(设置 / 修改 / 清除密码、待批准请求列表、授权下一次、批准有效期)。
破坏性变更:hardConfirmWindowMs 由 approvalPasswordTtlMs 取代;极高风险 ask 档不再产生人工审批弹窗(改走密码闸门);审计 outcome 用 password-required / password-approved 取代 hard-ask-1 / hard-confirm-2。
v0.3.1:密码批准从「极高风险专属」升级为四档通用方式
- 「需批准密码」不再是极高风险专属:低 / 中 / 高 / 极高任一档都能选
password; - 入口从「去设置页批准」升级为插件自己的密码弹窗(注册进
shell.overlay,浮在整个应用之上):挂起的调用当场放行,不再需要模型重试;设置页区块与/dsh-permission-matrix/approve批准页保留为等价入口; - 命名与配置键去「hard」化:
hardApproval.js→password-approval.js、hardApprovalPasswordHash→approvalPasswordHash、hardApprovalTtlMs→approvalPasswordTtlMs;旧键保留兼容读取并会在下次设置密码时自动迁移,已设口令不受影响; ask与password明确区分:ask仍是浏览器审批弹窗(点同意即放行),password必须知道口令;极高风险档两者等价。
参考与致谢
本插件的设计参考了以下三个社区插件(思路借鉴 + 取舍改进,代码为独立实现):
1. dsh-auto-classifier — 主要参考
- 借鉴:单点
auto预设的定位(低风险放行 / 危险拦截 / 沙箱升级自动裁决)、tools/pre-execute+approval/request双钩子 +{prepend:true}抢占、HARD/SOFT 风险分级、Tool(pattern)规则语法、字段投影(命令类只扫command/code、路径类只扫file_path)、工作区内结构放行、LLM 裁判两阶段(快速过滤 + 思考复审)、拒绝日志与 denial 上限、Git 快照。 - 改进:把「单点 auto」升级为 3 沙箱 × 4 审批 = 9 个可组合预设;风险分级从 2 档细化为低/中/高 + 极高,且四档各自可配(极高档只允许拒绝 / 转人工(需批准密码));新增执行面识别以消除"提及即拦截";新增全局默认 + LLM 机器人默认两个维度。
- 修正:该插件用
permissionPresets.current(session.events)(旧签名),在 DSH 0.1.2-rc.1 下会被try/catch静默吞掉而失效;本插件改用正确签名current(session)并回退到投影读取。
2. dsh-auto-approval-plugin — 审批应答者参考
- 借鉴:注册
approval/request监听器返回allowed-once实现「自动批准」、从会话日志按callId取真实工具参数(不信任模型写的理由)、路径经realpath解析后判定区域归属、失败方向永远defer(绝不误拒)。 - 改进:本插件的「自动同意」档同样带硬拒绝保护(极高风险不自动放行),而非无条件放行。
3. dsh-yolo-mode — 分级裁决参考
- 借鉴:用大模型裁决沙箱升权申请、
allow / judge / delegate / deny四值决策、fail-closed(超时/非法输出/模型不可用一律拒绝或转人工)、审计 JSONL 每行一次裁决。 - 改进:本插件把「转人工」做成中/高三档的可选策略,并解决了「完全权限下无审批通道」的限制——利用
tools/pre-execute返回{kind:'ask'}经 approval seam 转人工(见设计说明书 §0.1)。
其它参考
- DSH 官方文档:
docs/subsystems/approval.zh.md(审批 seam 语义)、docs/cookbook/extension-cookbook.zh.md(permission-gate 范例)、docs/cookbook/adding-a-settings-card.zh.md(设置页两个半侧)。 - 硬拒绝清单的条目集合合并自上述三个插件的默认 deny/dangerous 规则表。
License
MIT
Links
More in this category
toby-bridges/api-relay-audit★ 832
Runs local security audits of AI API relays and LLM proxies from DeepSeek Harness, producing Markdown reports for prompt injection, model substitution signals, tool-call rewriting, error leakage, stream integrity, and profile-gated Web3 risks.
howmp/dsh-pentest★ 451
Authorized pentest mode for DeepSeek Harness — exploration chain, assets and findings with a Web view.
SeaOf0/dsh-redteam-model★ 417
Authorized-security DSH collection: nine work modes (redteam coordinator, pentest, code audit, binary analysis, attack-defense, AV evasion, incident response, cloud security, CTF solving) and fifteen runtime plugins, managed from a settings page with one-click deploy, install, update and uninstall.
PerryLink/dsh-auto-review★ 164
Second-model auto-review on the approval answerer chain: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default.
PerryLink/dsh-permission-rules★ 114
Claude Code-style declarative permission rules: ordered allow/deny/ask YAML rules matching tool names, arguments, workspace paths, and agent identity on the tools/pre-execute waterfall, with full session-log audit, dry-run mode, and hot reload.
PensiveFei/dsh-secure-audit★ 85
Read-only security and compliance plugin for DeepSeek Harness: prompt-injection detection, Chinese-PII redaction, and a local configuration audit with redacted, reproducible reports.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.