dsh-trust-check
Đã xác minhdsh-trust-check · v0.2.1 · MIT · Giao diện web
Static capability disclosure for DeepSeek Harness plugins: capabilities, injected prompts, install scripts, and provenance, each with file:line evidence. Facts, not a safety verdict.
Cài đặt
dsh plugin add dsh-trust-check Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Thẻ
Readme
dsh-trust-check
DeepSeek Harness 插件静态能力披露:权限、注入、来源、安装脚本——代码判定、可复现、零 token。
不是"杀毒软件",也不做安全承诺。它只做能证明的事:把插件真实会碰什么、注入了什么、来源是否可核对摊开给你看,结论每一条都附证据(文件 + 行号 + 片段),你可以自己复核。
本项目只产出事实,不下"安全 / 可信"结论,也不提供背书;过滤策略由集成方和用户决定。详见 产品定位与边界。
安装
设置页需要 dsh ≥ 0.1.0-rc.8(建议 0.1.1-rc.2)。 Web UI 依赖宿主模块表里的 @deepseek-ai/dsh-client-store;市场不会拦不匹配的升级,老宿主上装了设置页也会加载失败(dsh-client-store missed the module table)。
CLI 不依赖 DSH 宿主,旧 dsh 上仍可用 npx dsh-trust-check。
先确认宿主(只影响设置页):
dsh --version
# 过旧则:npm i -g @deepseek-ai/dsh@latest
dsh plugin --profile web add dsh-trust-check
重启 dsh web,打开 设置 → 插件体检。已安装要升级:市场一键更新,或 dsh plugin --profile web add dsh-trust-check@latest,再重启。
同时提供独立 CLI,不依赖 DSH 宿主:
npx dsh-trust-check # 审计默认 profile `web`
npx dsh-trust-check --profile work # 审计其他 profile
npx dsh-trust-check --json # 机器可读输出
# 审计任意已解压的包目录(无需 profile、无需 DSH)
npx dsh-trust-check --dir ./path/to/plugin
npx dsh-trust-check --dir ./pkg --spec npm:[email protected] --json
# 用退出码做脚本 / CI 闸门(可选)
npx dsh-trust-check --dir ./pkg --exit-code
--dir 与 --profile 互斥。两种模式的 --json 输出同为 AuditResponse 形状 { schemaVersion, profile, dir?, generatedAt, plugins, errors };单目录模式下 profile 为空字符串,dir 为绝对路径。详见 docs/audit-schema.md / docs/INTEGRATION.md。
默认退出码始终为 0。加 --exit-code 后:0 未检出(profile 模式含已确认),1 需确认,2 有红线,3 扫描失败;profile 模式取所有插件中最严重的一项。0 只代表本次静态扫描没有检出,不代表安全。 参数写错(未知参数、--dir 等缺值)会直接报错并以 1 退出。
| 设置 → 插件体检 | CLI --dir --json |
|---|---|
![]() |
![]() |
排障
| 碰到 | 怎么处理 |
|---|---|
设置页 dsh-client-store missed the module table |
宿主过旧:先升 dsh ≥ 0.1.0-rc.8,再重启 Web;审计可先用上面的 CLI |
| 设置里没有「插件体检」 | 确认已 add、已重启;或宿主太旧导致客户端未加载 |
怎么读报告
设置页与 CLI 采用决策优先布局,阅读顺序:
- 决策:徽章 + 动作句 +「为什么要小心」(最多 3 条)
- 扫描:能力芯片 → 注入摘要(默认折叠)→ 来源
- 取证:证据按能力分组,默认折叠
| 裁决 | 含义 | 何时出现 |
|---|---|---|
| 有红线 | 命中硬红线,默认应停用;可「确认风险」后继续 | redLines 非空,且未确认当前指纹 |
| 风险已确认 | 已确认当前红线风险 | 有红线,trust-ack.json 与本次扫描一致 |
| 需确认 | 未见硬红线,但有特权能力或 patch 改动 | 有能力或 override/disable,无红线 |
| 能力如预期 | 已确认当前能力与去向指纹 | trust-ack.json 与本次扫描一致(无红线) |
| 静态未见风险信号 | 未见红线或特权能力;不是安全保证 | 其余 |
score / summary 仍留在 JSON 里供排序与集成方使用,设置页不展示;注入 token 估算标明为成本参考,不是信任依据。
形状层(代码判定)
除能力芯片外,报告还会从源码中提取三类字面量事实:
- 字面量去向:URL / host / IP。同源 HTTP 相对路由(如
/dsh-market/check)不算去向。每条去向带用法标签(请求参数、仅比较、XML 命名空间、链接、赋值/配置、用途未知)和文件:行号。标签只帮助判断,不改变能力、红线或分数。 - 工作区外路径:绝对路径、家目录、路径穿越等。
- 密钥触摸:路径、敏感 env 名。
这些只代表「在源码里看到了什么」,不代表「地址/路径安全」;运行时拼接的 URL 看不到(模板 URL 的 ${…} 若在主机名之后,或紧跟在完整的带点主机名后面(`https://api.github.com${path}`),主机名仍会记为去向;${…} 出现在主机名写完之前则看不到)。
白名单:常见托管/registry 域名(GitHub、npm、npmmirror、腾讯云镜像等)、DSH 精选目录与 GitHub 代理、常见模型厂商 API(DeepSeek、OpenAI、Anthropic、Google Gemini)。命中白名单的条目默认收起并附简短说明;明文 HTTP 永远不会因白名单降级。
降噪与截断:
- 跳过网络对齐的 CIDR 表行(如
["10.0.0.0", 8]、inRange(a, "10.0.0.0", "10.255.255.255"));["8.8.8.8", 32]这类主机路由仍记为字面量 IP。不因行内出现PRIVATE_RANGES/CIDR字样、或同行两个 IP 就整行跳过。 - 跳过 RFC 5737 文档例网(
192.0.2.0/24、198.51.100.0/24、203.0.113.0/24),以及0.0.0.0/255.255.255.255(绑定/广播,非单播出站)。 - 跳过
http://local/http://dsh.invalid一类占位 base、RFC 2606 的.example/.invalid/.test、cmd 开关(/c)等误判噪音。 - 注释在扫描前被抹掉,JSDoc 里的示例 URL 不算去向(打包产物通常保留注释)。
xmlns="http://www.w3.org/2000/svg"、pdf.js 的http://www.xfa.org/schema/…、http://ns.adobe.com/xdp/一类命名空间标识按主机名精确排除——攻击者注册不到这些域名,这条豁免无法被借用。只有带路径的字面量才豁免:不带路径的"http://www.w3.org"后面拼上任何内容都会变成别的主机,所以仍记为去向。- 打包进来的格式库里,少数完整 URL 只是标识而非请求(Apple plist 的 DTD
http://www.apple.com/DTDs/PropertyList-1.0.dtd、ID3 的http://musicbrainz.org),按整串字面量精确排除;同主机的其他路径、子域名,或字面量后面紧跟+/.concat(拼接,仍记为去向。不带路径的http://musicbrainz.org只在作为==/===/!=/!==的比较对象时豁免。 - 超出上限时按风险高低截断,明文 HTTP 与字面量 IP 不会被无害地址挤掉。
- 密钥路径只认无空白的路径形引号串(
"~/.ssh/config"、"/Users/x/.ssh/config"、'.ssh/config'、"~/.aws/credentials")或/id_rsa、~/.netrc等路径形态;UI 文案("Uses … ~/.ssh/config when empty")、deny-list 正则、startsWith('id_rsa')、裸'.ssh'不算凭据访问。
记为预期
在设置页确认「这些能力符合我装它的目的」后,指纹写入 ~/.dsh/profiles/<profile>/trust-ack.json。升级后能力、去向、工作区外路径、密钥触摸、红线或技能正文(按内容哈希,而不是字节数)任一变化,都会要求重新确认。确认请求必须带上当前报告的 ackFingerprint;内容已经变化时返回 409,并提示重新确认。没有 digest 的旧确认一律失效。确认红线走单独的「确认风险」按钮,而且只在指纹一致之后才接受。
AI 解释:可选按钮,通过 DSH 已配置的模型解释报告摘要,不改裁决;未配置模型时不可用。
红线(有则默认应挡住)
- 声明 install / postinstall / preinstall 安装脚本(不含
prepare:prepare只在 pack/git 安装时跑,记为扣分,不是红线); cordis.patch.ymloverride / disable 了@deepseek-ai/*核心 bundle(匹配id或name);- 读到凭据/密钥的值(
credentials.resolve/read/readRecord/get*,含经接缝别名 / 解构改名的调用、keytar/keychain的getPassword等——含import * as/default as改名——或读文件调用同行的密钥路径)且有网络访问——只拿到句柄(const c = ctx.credentials、ctx.get('credentials'))或只读元数据(describe)记为 chip 披露,不算红线; - 非 localhost、非 RFC 1918 内网 IP 的明文
http://外连(字面量)且有 network; - 非 loopback、非文档例网、非绑定/广播、非 RFC 1918 内网(
10/8、172.16/12、192.168/16)的字面量 IP 外连 且有 network。内网 IP 仍列在去向里并标「内网/私人」,只是不判红线——远程攻击者收不到发往内网的数据;169.254/16(含云元数据地址)不在此列,照常判红线。
命中红线时数值分封顶 49(避免「100 分 + 高风险」的误导)。裁决只看 redLines,不看分数:分数低(如 9 分)可能只是 shell + 网络 + 未锁版本叠加,应显示「需确认」而非「有红线」;JSON 里的 band 仍可能为 red(分数低于 50),但 UI/CLI 用 verdict() 呈现,二者不要混读。
审计什么
| 维度 | 读什么 | 判定 |
|---|---|---|
| 能力面 | package.json 依赖 scope + 静态扫 lib/、dist/、bin/、scripts/、技能目录,以及 main / exports / bin / 字符串 browser 入口文件。files 能精确解析时,npm 不会发布的开发文件不计入 |
shell / 文件读写 / 网络 / 凭据 / 子代理 / LLM 调用 / 环境变量 |
| 注入面 | cordis.patch.yml + systemPrompt / ctx.skills.register / system-prompt/assemble + 技能文本 |
override / disable 了谁(id 或 name)、注入了什么 |
| 成本 | 技能文本 + system-prompt 行内字面量字节数 | 估算每请求注入 token(字节 / 4,仅估算) |
| 来源 | package.json 的 repository(缺失回退到 git 安装源)+ 安装 spec |
是否锁版本/锁 commit |
| 更新风险 | 安装脚本(install/postinstall/preinstall;prepare 仅扣分) |
是否声明了安装时执行的脚本 |
给集成方(如插件目录、市场、CI)
装前怎么接闸门:docs/INTEGRATION.md(英文)。
JSON 字段契约:docs/audit-schema.md(schemaVersion、稳定五字段、capabilities 取值与红线文案契约及建议中文、易变字段)。
版本变更:CHANGELOG.md,每个版本先列「Affects catalog results」,锁定版本的集成方升级前看这一节即可。
真实接入参考:awesome-dsh-plugin 在目录构建期调用本工具,适配层见 scripts/lib/capabilities.mjs(第三方代码,由对方维护)。
本包导出稳定 API,供安装前确认弹窗或 CI 闸门使用:
import { auditPlugin, collectPlugin, verdict } from 'dsh-trust-check'
const report = auditPlugin(collectPlugin(extractedDir, spec))
// 三态含义(是否拦截、是否弹窗由集成方决定):
// verdict(report) === 'red' → 命中高置信度风险信号,建议默认挡住,允许用户确认后继续
// verdict(report) === 'review' → 带特权能力,建议展示能力清单
// verdict(report) === 'clear' → 本次静态扫描未检出,不代表安全
CLI 等价调用(market 也可 spawn,无需 DSH):
npx dsh-trust-check --dir "$EXTRACTED_DIR" --spec "$INSTALL_SPEC" --json
解析 --json 时统一读 plugins[0](单目录)或 plugins 数组(profile 模式);errors 非空表示目录不可读——按扫描失败处理,不是 clear。空目录 / 损坏解压(无可读 package.json 且无源码)会进 errors(fail closed)。--json 顶层含 schemaVersion(当前为 2):输出形状出现破坏性变更,或新增集成方可以依赖的稳定字段时递增,检测规则改动不会 bump。schema 2 新增 facts[],其中 facts[].id 是稳定的过滤键;schema 1 的字段没有删改,能力取值和红线模板也与 schema 1 相同。细节见上两份文档;仓库内 Path A 冒烟样例:scripts/market-gate-demo.mjs。
本期不做:远程 tarball 下载(拉包是 market 的职责)。独立验证姿势:先把包解到临时目录,再 --dir。
判定原则
- 代码判定,不是 LLM 打分:判断全在代码里,不烧 token、结果可复现。
- 只证"有",不证"无":静态分析只下"检测到了某能力"的结论,从不说"保证没有某能力"。
- 证据可复核:每个能力命中都带
文件:行号和原文片段。 - seam 表可热更:能力判定规则是一张数据表(
src/core/seams.ts),DSH 接口变了改表不改引擎。
已知局限
- 装后体检:profile 模式审计的是已经安装的插件;install/postinstall/prepare 在你第一次扫描前就可能已经跑过。
--dir模式可在安装前对解压目录扫描(但安装脚本本身仍可能在 market 解包/安装阶段已执行)。 - 静态扫描有漏判/误判(运行时才加载的能力看不到;动态
import('node:' + …)、字符串拼接、混淆后的eval/Function仍可能绕过规则表)。 - 不展开依赖树:目录遍历跳过
node_modules,import 'lodash'这类包名不跟。相对路径如果落在本包node_modules里,会跟进去扫。 - 凭据读值判定覆盖到哪些写法:接缝服务(
ctx.get('credentials'),含await/ 可选链get?.;接收者认ctx/this.ctx/*Ctx)、属性直取(ctx.credentials)、从上下文解构(含改名credentials: creds)、以及经这些来源再赋值的别名(含b = a),其上的resolve/read/readRecord/get*都算读到值;keytar/keychain的getPassword/findCredentials等,包括默认导入、import * as、import { default as … }、require改名也算。仍漏判:解构到函数名(const { resolve } = ctx.credentials后resolve(…))——它与 Promise executor 同名,按行匹配会把new Promise(resolve => …)误判成读值(实测会把dsh-pocket、agent-teams误红),需要作用域追踪;密钥路径先存进变量再readFileSync(p)同样漏判;接收者若既不叫ctx也不以Ctx结尾(如context/Context)也不认。 new URL的 base 参数不记为去向,因此const u = new URL('/x', 'http://evil'); fetch(u.href)这种写法不含明文 http 红线——这是该豁免的已知代价,不要再扩大 base 豁免范围。- 完整的相对路径字面量(
fetch('/api')、fetch('./x'))以及blob:/data:字面量不算 network——前者是同源调用,后者只走 scheme fetch、不会发起 HTTP。引号里的${只是路径字符。反引号里,插值前面已经是/加至少一个非斜杠字符,或已经是./、../时,也算同源(fetch(`/dsh-xu/${name}`))。fetch(`/${name}`)、fetch(`${url}`)、变量参数和拼接仍记为 network;\、\n、\u以及八进制\057折成/之后变成//主机的也记为 network。芯片会标「同源」或「外连」,但没有字面量外连不等于不出网,评分不变。 link:/ 源码目录在package.json的files能精确解析时,不把 npm 不会发布的开发文件算进能力和红线(被发布代码 import 的、安装脚本点名的、入口文件仍然算)。*表示全部路径,dir/*表示该目录整棵树;src*不含src/里的文件。花括号、字符类、extglob、反斜杠写不准则不过滤。证据上的「浏览器端 / 服务端 / 命令行 / 疑似第三方」来自入口可达性和 sourcemap,只供阅读,不改分数。registry 解压出来的包本来就只有发布文件。- 注入 token 是字节 / 4 的粗估,不是精确计费。
link:/file:本地安装的插件无法从 spec 推断来源,若其package.json未声明repository,会显示"未声明仓库"。repository字段是插件自述,不与 npm 包名交叉验证;非http(s)协议不会渲染成可点击链接。
开发
pnpm install
pnpm build # tsdown:node half → lib/index.js,client half → lib/client.js
pnpm test # vitest,覆盖 core 引擎
pnpm typecheck # tsc --noEmit
规则表、跳过规则与白名单的贡献流程见 CONTRIBUTING.md。扩大跳过 / 白名单与加规则不同权——二者都可能削弱检测;明文 HTTP 永远不会因白名单降级。修误报时必须带 fail-open 探针。规则打磨对照的攻击面与静态分析极限见 THREAT-MODEL.md(英文)。
路线图
- 当前:已装插件体检 + CLI
--dir+ Web 分项报告;持续加固扫描覆盖面 - 集成:awesome-dsh-plugin 已在目录构建期接入扫描,dsh-market 与目录站只陈列事实(由对方维护,可替换)
- v2:结构化事实
facts[](schemaVersion: 2)从 0.2.0 起提供;接下来是版本间升级漂移对比 + 调用方传入 registry 元数据 - 之后:配合目录侧的版本升级与误报反馈;CI 中的升级漂移断言
- 有条件再做:社区具名人工审阅,独立仓库、绑定版本、可撤销
完整说明见 docs/POSITIONING.md。
License
MIT

