Перейти к основному содержимому
S

dsh-plugin-security-review

shanhaifish/dsh-plugin-security-review

Static bundle form of the DSH plugin-install security gate: reviews cordis_define/cordis_run with a fail-safe policy, plus review/audit tools and a browser approval popup (agree / agree+whitelist / reject; agree+whitelist writes the plugin family into tru

Установка

dsh plugin --profile web add github:shanhaifish/dsh-plugin-security-review

README

Plugin Install Security Review(v1.8.0)

English: README.en.md · 简体中文: 本文档

npm version npm downloads License: MIT

DSH 插件安装安全审查守卫。对 cordis_define / cordis_run 安装或运行的动态插件源码做静态安全审查,并按安全优先策略拦截;同时提供 plugin_security_review / plugin_security_audit 两个审查工具。

v1.5.0 起为静态 bundle 插件,随 profile 层栈自动加载,不再需要每次重启 DSH 后重新 define/run。

仓库内容

路径说明
package.json + cordis.patch.yml + lib/静态 bundle(推荐):dsh plugin add 安装后开机自启动
manifest.json + package-source.js动态插件回退形态(v1.4.0):无 bundle 能力的 profile 按 README 恢复流程加载
tests/gate.test.mjs动态形态行为测试(node tests/gate.test.mjs)
tests/static.test.mjs静态形态行为测试(node tests/static.test.mjs)

安装(静态 bundle, 推荐)

dsh plugin --profile web add dsh-plugin-security-review

已发布到 npm(registry.npmjs.org/dsh-plugin-security-review,最新 v1.8.0)。生产环境直接用上述命令从 registry 安装即可;file: 本地安装仅用于未发布或离线调试。

本地未发布时用 file: 指向本仓库(注意路径不能含空格):

dsh plugin --profile web add file:/path/to/dsh-plugin-security-review

重启 dsh web 后守卫即自动生效:plugin_security_audit(无参数)应显示「守卫: 运行中 (v1.8.0)」。

安装(动态插件, 回退)

仅用于没有 bundle 能力的 profile。步骤:

  1. 让 agent 读取 package-source.jsmanifest.json
  2. cordis_define:plugin: { kind:"new", idPrefix:"secur" },name/purpose 取 manifest.json,code.hostpackage-source.jsreturn { ... } 之后的内容。
  3. cordis_run 激活;plugin_security_audit 验证。

动态形态不跨 DSH 进程存续,重启后需重新加载;静态 bundle 形态无此限制。

v1.8.0 变更(相对 v1.7.0)

  • 审批弹窗新增「同意+白名单」:弹窗由 [同意][拒绝] 改为 [拒绝] [同意+白名单] [同意]。点「同意+白名单」→ 除把该代码指纹写入 approved 外,还把该插件族加入 trusted-local 白名单(new define 按 idPrefixprefixes;existing/run 按 pluginIdpluginIds)——同一插件的安装与开发迭代(code 变更导致指纹变化)不再反复弹窗。点「同意」仍仅批准该代码指纹(代码变更即失效)。白名单写入 best-effort:写失败(权限/损坏)只记日志、不影响指纹批准,读取侧 fail-closed 不变。
  • 修复:点「同意」后重试消息偶发投到别的会话。旧实现用浏览器端单一 sessionId,从 mux 流任意 session/subscribed 帧取值;DSh mux 是全会话聚合的,多会话并存时最后一个订阅者覆盖该值,导致 session.prompt 把重试注入到其它工作对话。现改为守卫在拦截时记录来源会话 id(exec.agent.sessionId,写入每个 pending 条目的 agentId),弹窗点击后经 connection.api.sessions.prompt 注入到来源会话;agentId 缺失或目标不可 prompt 时静默退回手动重试。
  • 弹窗 hint/按钮文案与 README 同步更新;新增 T19(来源会话 id 暴露)与 T20(同意+白名单写白名单、同族变体不再弹窗)测试。
  • 动态回退形态(package-source.js/manifest.json)同步版本号至 v1.8.0,但不提供弹窗(浏览器 GUI 特性,仅静态 bundle;其 ASK 仍走 seam)。

v1.7.0 变更(相对 v1.6.0)

  • 审批弹窗(同意/拒绝, 新增, 静态 bundle):ASK 判定不再走官方审批 seam(在审批禁用部署中 seam 会被策略自动拒绝成 the user rejected tool 死路),改为挂起审批 + deny + 浏览器弹窗。用户点击「同意」→ 代码指纹持久化批准 → 注入会话消息让 agent 自动重试 → 安装/运行成功;点「拒绝」→ 记录并放弃。弹窗经守卫自己的 HTTP 路由(/security-gate/approvals/pending/security-gate/approvals/decide)轮询驱动,渲染在 shell.overlay 浮层;携带 client/client.js Client 半端(dsh.client 声明 + exports["./client"])。
  • askMode 配置:<DSH_HOME>/storages/plugin-security-gate/config.json{"askMode":"seam"} 可回退官方审批 UI;默认(缺失/损坏)popup
  • 弹窗样式与主题适配:居中模态 + 半透明遮罩(替代早期侧边浮层, 消除遮挡);全部颜色使用 dsw 设计系统真实令牌(--dsw-alias-bg-overlay/--dsw-alias-button-primary-fill/--dsw-alias-label-primary-foreground/--dsw-alias-bg-mask-3/--dsw-alias-state-error-primary/--dsw-alias-state-warn-label 等),亮/暗主题自动适配,保证字体与窗口可读性。
  • audit 状态展示:总览增加 askMode待审批 数量;gateStatus() 同步新增字段。
  • 动态回退形态版本同步 v1.7.0,但不提供弹窗(浏览器 GUI 特性,仅静态 bundle;其 ASK 仍走 seam),README 明示该差异。

v1.6.0 变更(相对 v1.5.0)

  • 操作者白名单 trusted-local(新增):<DSH_HOME>/storages/plugin-security-gate/trusted-local.json 声明本机可信插件(前缀/pluginId/代码指纹),命中时 cordis_define/cordis_run 直接放行并写入审计历史([trusted-local])。信任锚是有文件权限的操作者,不是 agent 自我声明;文件不存在或损坏时行为与现状完全一致(fail-closed 不变)。cordis_stop/cordis_undefine 的守卫自我保护不受白名单影响。
  • 修复 audit lossless-JSON 报错:plugin_security_audit 不带 includeBundles 时返回 bundles: undefined,违反 dsh-tools 的 lossless-JSON 输出校验导致工具报错;现默认 null
  • BLOCK 拒绝文案修正:BLOCK 没有人工批准通道,文案不再声称"批准可放行",改指向 trusted-local 白名单;组合规则(HOST_EXFIL_CRED/CLIENT_COOKIE_EXFIL 等)标注 [不可声明削减],明确不受 CAPABILITIES: 声明影响。
  • 动态回退形态(package-source.js/manifest.json)同步到 v1.6.0。

v1.5.0 变更(相对 v1.4.0 动态形态)

  • 静态 bundle 化:命名导出 name/inject/apply,经 ctx.tools.register(defineTool(...)) 注册工具,经 ctx.on('tools/pre-execute') 拦截,开机自启动。
  • 移除动态自升级豁免:静态形态经 dsh plugin update 升级,cordis_define/cordis_run 对所有动态插件一律审查,不再有 secur 前缀/谱系令牌豁免;源码不可检索时一律 fail-closed(ask)。
  • 审查引擎、评分模型、能力声明、跨会话批准持久化、库存审计与 v1.4.0 保持一致。

判定策略(安全优先)

判定条件行为
BLOCKcritical>0 或 high≥2 或 总分≥100拒绝安装/运行,附完整报告(不经弹窗)
ASKhigh≥1 或 总分≥40askMode=popup(默认):挂起审批 + 弹出「同意/拒绝」确认框,同意后指纹批准并自动重试;askMode=seam:走官方审批服务(审批禁用时等效拒绝)
WARN总分≥10放行,卡片附审查报告
ALLOW其余放行

风险权重:critical=100, high=40, medium=15, low=4,总分上限 300;能力声明项计分减半(最低 1 分)。

审查覆盖面

  • Host 规则:不安全进程执行(exec/execSync/shell:true — critical)、VM 逃逸、Node 内部 API、构造器链逃逸、宿主进程终止(critical);子进程执行能力(spawn/fork — high)、原型污染、动态代码、凭据访问、审批篡改、动态模块加载(high);静态模块加载、进程信号、文件系统、网络、全局设置、沙箱、工具干预、会话读取(medium);普通 env 读取、定时器(low)。
  • 凭据形态环境变量:process.env.X 名字形如 *_KEY/_TOKEN/_SECRET/_PASSWORD/_CREDENTIAL/_AUTH/_COOKIE/_PRIVATE 单独记 high。
  • Client 规则:document.cookieinnerHTML XSS、浏览器 eval、动态模块加载(high);import()/require、存储、网络、跳转、postMessage、Service Worker(medium);host.call、定时器(low)。
  • 15 项服务能力面:ctx.get('shell'/'subprocess'/'credentials'/'approval'/'dynamicCordisRunner'/...) 精确检测。
  • 组合规则(收敛后):凭据形态数据+网络 = critical;Cookie+网络 = critical;文件读取+网络 = high;动态代码+网络 = high;本地存储+网络 = low(信息性)。
  • 混淆检测:高熵长字符串、\x/\u 转义、atob

能力声明约定(可选)

cordis_definepurpose 末尾追加:

CAPABILITIES: spawn,network,env,fs

关键字:spawn exec module network env fs credentials approval shell subprocess runner settings sandbox sessions llm process eval vm proto storage cookie dom redirect postmessage serviceworker rpc timer obfs。声明覆盖的规则项计分减半(报告标 [已声明])。

跨会话批准持久化

  • 人工批准的 ASK 成功执行后,其代码指纹(host+client 的 sha256)写入 <DSH_HOME>/storages/plugin-security-gate/state.json
  • 后续 define/run 命中相同指纹自动放行;代码变更即失效。历史尾部一并持久化。

本地可信开发:trusted-local 白名单(v1.6.0)

背景:守卫对所有 cordis_define/cordis_run 一律审查,合法的本地开发插件也可能被 BLOCK/ASK 卡死(例如"读取 DEEPSEEK_API_KEY 并调用官方 API"这类插件触发 HOST_EXFIL_CRED critical 组合,BLOCK 无人工批准通道)。不做"本地创建自动放行":DSH 工具调用契约里没有来源/出处字段,"本地编写"与"提示注入伪造"在调用面上不可区分,自动放行等于重新引入 v1.4.0 修复过的豁免伪造漏洞。放行必须绑定 agent 自我声明之外的信任锚——操作者文件白名单。

格式(由有文件权限的操作者创建;文件不存在/损坏 = 无白名单,行为不变):

{
  "prefixes": ["dev", "local"],
  "pluginIds": ["devtool-1", "legit-llm-client"],
  "fingerprints": ["<host+client 代码指纹 sha256, 可用 plugin_security_review 结果中的 sha 字段>"]
}

匹配规则(命中任一即直接放行,并记入审计历史 [trusted-local]):

条目匹配对象
prefixesdefine 的 idPrefix(kind:new)或 pluginId(kind:existing)前缀匹配
pluginIdsdefine 的 pluginId / run 的 pluginId 精确匹配(不依赖源码可检索)
fingerprintshost+client 联合 sha256 精确匹配(代码变更即失效)

边界说明:

  • 白名单放行同样适用于 BLOCK 级插件——这是"操作者显式信任"的意图所在。
  • 把守卫自身 id/前缀加入白名单会解除守卫自我保护(操作者本就有文件权限卸载守卫,风险自担)。
  • cordis_stop/cordis_undefine 对守卫的自我保护不受白名单影响。
  • 在 full-access 部署中 agent 也能写该文件——这与 agent 已有全部文件权限的信任级别一致,白名单是对抗"未经操作者确认的高危安装",不是对抗 agent 自身的安全边界。

审批弹窗:拒绝 / 同意+白名单 / 同意(v1.8.0, 静态 bundle)

为什么需要:ASK 判定走官方审批 seam 时,在审批禁用部署(策略自动拒绝)中会变成 the user rejected tool "cordis_define" 死路。v1.7.0 起静态 bundle 形态自带弹窗,ASK 不再无出口。

流程:

  1. cordis_define/cordis_run 触发 ASK 判定 → 守卫挂起审批记录(持久化于 state.json,并记录来源会话 id agentId)并返回 deny,文案提示"已弹出确认框"。
  2. 浏览器端 client/client.js(注册在 shell.overlay 浮层)轮询 GET /security-gate/approvals/pending,弹出卡片:插件名、判定/风险分、风险分布 + [拒绝] [同意+白名单] [同意]
  3. 点「同意」→ POST /security-gate/approvals/decide {sha, outcome:'approve'} → 守卫把该代码指纹写入 approved → 经 session.prompt来源会话注入重试消息 → agent 自动重试 → 同指纹放行。
  4. 点「同意+白名单」→ POST .../decide {sha, outcome:'approveTrusted'} → 同第 3 步外,再把该插件族写入 trusted-local(prefixes/pluginIds)→ 同族后续安装/开发(即使代码变更)直接放行,不再弹窗。
  5. 点「拒绝」→ 记录 human rejected via popup 历史并清除挂起;agent 重试同代码仍被拦截。

配置 askMode(<DSH_HOME>/storages/plugin-security-gate/config.json):

{ "askMode": "seam" }
  • popup(默认,缺失/损坏时回退):守卫自带弹窗;绕过官方审批 UI。
  • seam:走官方 approval 服务(需要部署启用审批,否则等效拒绝)。

边界:

  • 弹窗是浏览器 GUI 特性:仅 web profile 的静态 bundle 形态提供;CLI/无浏览器会话与动态回退形态的 ASK 仍走 seam(deny 文案附 trusted-local 手动指引兜底)。
  • 「同意」仅批准该代码指纹(代码变更即失效),不等于加入 trusted-local;「同意+白名单」才写入 trusted-local。
  • 重试注入固定指向来源会话(entry.agentId,守卫在拦截时从 exec.agent 记录),与当前浏览哪个会话无关;多会话并存不再投错。
  • BLOCK 判定仍硬拒绝,不经弹窗(安全优先不变)。
  • 多会话并存时挂起审批为全局注册表(本地单用户部署通常单会话);如需按会话过滤,后续可在路由增加 session 参数。

profile bundle 库存审计

plugin_security_audit includeBundles=true:扫描 <DSH_HOME>/profiles/* 下声明 dsh 的依赖(@deepseek-ai/* 官方包跳过),读取入口产物做同一套静态审查,输出逐包判定。信息性审计:bundle 安装(pnpm/npm)不经过 cordis_define/cordis_run,守卫无法在该路径硬拦截,仅提供装前/装后可见性。

工具

  • plugin_security_review:对给定 host/client 源码做安装前审查,返回判定、评分、逐条风险与行号;purpose 支持 CAPABILITIES: 声明。
  • plugin_security_audit:指定 pluginId+packageId 出完整报告;无参数出全量总览+拦截/放行历史+守卫状态;includeBundles=true 附加 profile 库存审计。

维护与边界

  • 升级守卫(静态):dsh plugin --profile web update dsh-plugin-security-review;本地 file: 依赖更新后重跑一次 dsh plugin add
  • 自我保护边界:静态 bundle 守卫无法通过 cordis_stop/cordis_undefine 停用(它不在动态注册表里),但可由有文件权限的操作者经 dsh plugin remove 卸载——这是静态形态与动态形态的固有差异。
  • 已知限制:组合判定是静态 presence 判定,未经数据流确认,可能误报/漏报;深度混淆存在盲区;本守卫是进程内拦截(标准安装路径),不是对抗可停用守卫的恶意 actor 的安全边界,真正的纵深防御仍需宿主级隔离。trusted-local 白名单与审批弹窗都是操作者/用户决策面,不是对抗 agent 自身的安全边界;审批弹窗仅静态 bundle 形态提供。

Похожие плагины