通过一次性网页链接安全传递密钥,避免密钥进入聊天记录
29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/22 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add unera/dsh-secret-form该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DeepSeek Harness 的一次性密钥链接:模型发起请求,你在网页表单里填写,值会写入凭据存储或文件。
README
由 DeepSeek 最新模型翻译生成dsh-secret-form
在 DeepSeek Harness 里,用一次性网页链接传递密钥。
模型需要 API key。现在的常见做法是「直接粘贴到聊天里」,可这样一来,密钥就进了会话日志、对话记录、之后每一次服务商请求,以及会话导出到的任何地方。这个插件换了个办法,改用链接:
1. 模型调用 secret_create,拿到一个绝对 URL;
2. 你在手机或电脑上打开它,输入值,点保存;
3. 插件把值写进宿主的凭据存储,或写进文档文件;
4. 模型调用 secret_result,得知 saved、length: 51 —— 永远拿不到值本身。
页面是一份自包含的 HTML 文档:内联 CSS,没有外部资源,没有框架,同一份标记同时适配浅色和深色。
安装
包内自带 dsh.bundle 层,所以 dsh plugin add 既安装又挂载:它会出现在设置里的插件列表中,可以像其他插件一样关闭或移除。宿主相关的设置应写进配置档自己的补丁,用 id 定位覆盖。
从 npm 安装
dsh plugin --profile <profile> add @dr-co/dsh-secret-form
从 GitHub 安装
想以后能指回某个版本,就固定一个 tag;仓库根目录就是包本身,不涉及构建步骤。
展开完整文档(README 剩余 1 节)
dsh-secret-form
在 DeepSeek Harness 里,用一次性网页链接传递密钥。
模型需要 API key。现在的常见做法是「直接粘贴到聊天里」,可这样一来,密钥就进了会话日志、对话记录、之后每一次服务商请求,以及会话导出到的任何地方。这个插件换了个办法,改用链接:
1. 模型调用 secret_create,拿到一个绝对 URL;
2. 你在手机或电脑上打开它,输入值,点保存;
3. 插件把值写进宿主的凭据存储,或写进文档文件;
4. 模型调用 secret_result,得知 saved、length: 51 —— 永远拿不到值本身。
页面是一份自包含的 HTML 文档:内联 CSS,没有外部资源,没有框架,同一份标记同时适配浅色和深色。
安装
包内自带 dsh.bundle 层,所以 dsh plugin add 既安装又挂载:它会出现在设置里的插件列表中,可以像其他插件一样关闭或移除。宿主相关的设置应写进配置档自己的补丁,用 id 定位覆盖。
从 npm 安装
dsh plugin --profile <profile> add @dr-co/dsh-secret-form
从 GitHub 安装
想以后能指回某个版本,就固定一个 tag;仓库根目录就是包本身,不涉及构建步骤。
dsh plugin --profile <profile> add "git+https://github.com/unera/dsh-secret-form.git#v0.1.0"
a particular commit
dsh plugin --profile <profile> add "git+https://github.com/unera/dsh-secret-form.git#<sha>"
or a local checkout while developing
dsh plugin --profile <profile> add file:$PWD/dsh-secret-form
两种方式下,包内都不带 dsh.bundle,所以它只会作为普通依赖落地,真正挂载它的是下面这行配置档补丁。
把它挂到它用到的两个服务旁边 —— Web GUI 宿主和智能体的工具:
- id: secret-form
name: '@dr-co/dsh-secret-form'
config:
routePrefix: /secret # default; must start with "/"
defaultTtlSeconds: 300 # default; every value is clamped to 30..3600
defaultLocale: en # default; en|ru|de|fr|es|pt|zh|ja
publicUrl: https://dsh.example.org # optional; see below
在代理后面时 publicUrl 很关键:监听器通常绑定回环地址,用它自己的 origin 会生成 http://127.0.0.1:3080/... 这样的链接,手机根本打不开。设了 publicUrl,返回的链接就用它拼;没设,插件原样返回监听器的 origin,并告诉模型直接把这个链接交出去,不要自己编一个主机名。
routePrefix 会移动整个接口面,宿主在路径重写后面时需要它。defaultTtlSeconds 是调用没传 ttlSeconds 时用的 TTL。defaultLocale 是链接和浏览器都没指定语言时用的文案;挂载时遇到其他值会直接拒绝。
插件声明了 inject: ['tools', 'webServer']:没有这两样它什么都做不了没有 Web GUI 服务器来提供页面,它把四个工具注册到智能体界面。凭据服务按需读取,所以只写文件的主机不需要凭据服务商。
四个工具
secret_create
打开表单并返回链接。
| 参数 | 类型 | 必填 | 含义 |
| ref | string | 是 | 键名。默认目标是凭据引用,例如 OPENAI_API_KEY。文件目标是文档内的路径 —— yaml/json 用 provider.apiKey,.env 用裸键名。 |
| label | string | 是 | 表单标题,2 到 5 个词。 |
| kind | 'single' \| 'multi' | 是 | 单行掩码输入框,或多行密文用的等宽文本框。 |
| description | string | 否 | 标题下一两句话,最多 300 字符。 |
| save | object | 否 | 默认 { "kind": "credentials" }。文件目标是 { "kind": "file", "path": "…" }。 |
| ttlSeconds | integer | 否 | 默认 300,限制在 30..3600。 |
| allowEmpty | boolean | 否 | 默认 false。为 true 时接受空提交,并报告为 empty。 |
| expect | string | 否 | 以文本形式给出的正则表达式。结果带 matchesExpect;不匹配时页面会警告,但仍然保存。 |
| data | object | 否 | 最多 1 KB,供模型记账用。四个工具都原样回传,从不在页面上渲染。 |
返回 { uuid, url, expiresAt, data }。url 是绝对地址:Web 服务器的 getOrigin() 加 routePrefix 加 43 字符 token。
secret_result
| 参数 | 类型 | 必填 | 含义 |
| uuid | string | 是 | secret_create 返回的 uuid。 |
| waitSeconds | integer | 否 | 0..30,默认 0。大于零时等待表单离开 pending,然后报告它到达的状态。 |
返回 { status, data, ref, label },一旦提交了值,还会带上 length(字符数)、lines、trimmed(是否去掉了首尾空白),以及给出 expect 时的 matchesExpect。status 取值为 pending、saved、empty、cancelled、expired 之一。
提交的值绝不会出现在工具结果里。不截断、不掩码、不哈希 —— 就是不出现。
secret_list
无参数。返回本进程创建过的所有表单,最早的在前:
[{ "uuid": "…", "ref": "OPENAI_API_KEY", "label": "OpenAI key", "status": "saved",
"createdAt": "2026-10-21T12:28:30.263Z", "expiresAt": "2026-10-21T12:33:30.263Z", "data": {} }]
secret_cancel
| 参数 | 类型 | 必填 | 含义 |
| uuid | string | 是 | secret_create 返回的 uuid。 |
取消待处理的表单;已经结束的表单会报告当前状态,不做改动。
Web 界面
| 路由 | 方法 | 请求体 | 结果 |
| <prefix>/<token> | GET、HEAD | — | 表单,或结果页 |
| <prefix>/<token> | POST | application/x-www-form-urlencoded,带 value、action(save 或 cancel)和隐藏字段 lang | 结果页 |
两个响应都带 Cache-Control: no-store、Referrer-Policy: no-referrer、 X-Content-Type-Options: nosniff、X-Frame-Options: DENY,以及
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline';
script-src 'unsafe-inline'; form-action 'self'; base-uri 'none'
标签和描述来自模型,按不可信 HTML 转义;页面上唯一的脚本是显示切换和 expect 提示,两者都内联,都来自本站点。
结果页的措辞各不相同:Saved、Nothing stored(空提交)、Cancelled、Link already used、Link expired、Link not recognised。
语言
页面自身的文案有 en、ru、de、fr、es、pt、zh、ja 八种,对照表放在 lib/i18n.mjs。语言按以下顺序选择:
1. 链接上的 ?lang=de(/secret/<token>?lang=de),模型也可以借此为交出去的链接固定一种语言;
2. 浏览器的 Accept-Language,按 q 值排序,按主语言子标签匹配,所以 de-AT 和 zh-CN/zh-TW/zh-Hans 分别解析为 de 和 zh;
3. defaultLocale,最后是英语。
表单用隐藏字段 lang 携带解析出的语言,提交时它的优先级高于请求头,所以结果页保持读者进来时的语言。 用同一个值。ref(OPENAI_API_KEY)是标识符,原样保留;它周围的文字都翻译。
值写到哪里
凭据(默认)。 ctx.credentials.set(ref, value) —— 适配器通过 apiKeyEnv 读的就是同一个存储,所以轮换后的密钥对下一次请求立即生效。存储拒绝空值,所以空提交会报 empty,什么也不存。
文件。 { "kind": "file", "path": "/abs/or/relative/path" }。格式由扩展名决定,别的都不管 —— .yaml、.yml、.json、.env;其他扩展名在创建表单时就拒绝,绝不猜测。
- .env —— 追加 KEY=value 行,或就地替换已有的那一行。含空格、引号或换行的值用双引号包裹并转义。
- .json —— 解析,设置点分路径,以 2 空格缩进重新序列化。
- .yaml / .yml —— 解析,设置点分路径,重新序列化。
写入会创建父目录,先写到目标旁边的临时文件再 rename,最终权限为 0600。嵌套对象缺失时创建;如果路径上已有标量却需要映射,或叶子位置已有映射,则拒绝写入而不是破坏原有内容。__proto__、prototype 和 constructor 作为路径段一律拒绝。
YAML 写入器刻意做得很小
读取器支持块映射、普通标量和带引号标量、注释和空行。其他一概不支持 —— 序列、流式集合、块标量(|、 >)、锚点、别名、标签、多文档——一律报错拒绝,文件原样保留。理由很简单:只理解一半就改写文档,比直接拒绝更糟。叶子值保留原始写法(port: 8080 仍是 port: 8080);改写会丢掉注释。手写 YAML 文件需要保留注释时,改用 .json 或 .env。
安全说明
- token 就是权限凭证。 256 位 crypto.randomBytes,base64url 编码,一次性使用。提交成功或取消都会让它失效:链接仍能打开,但只会显示「已使用」或「已取消」。其他任何东西都读不到表单。
- 值只从浏览器流向宿主。 不写日志,不回显到页面,也不返回给模型。日志最多记录 token 的前八个字符。
- 页面绝不接受同一个值两次。 对同一 token 的第二次 POST 返回 410,不写入任何内容。
- 页面不加载任何第三方内容。 default-src 'none',只允许内联样式和脚本,form-action 'self',base-uri 'none',无 referrer,不存储,不允许被嵌入框架。
- 能读聊天记录的人都能看到一次性链接。 URL 不含机密,可以放心贴出;但谁能先提交,谁就赢。把窗口当作 ttlSeconds,不再需要的表单及时取消。
- 链接绑定当前进程。 链接不做任何持久化,所以重启是安全的——也正因如此,所有表单会随重启一起消失。
设计上只存内存
表单存在插件实例的 Map 里。没有数据库,没有文件,磁盘上没有 token。由此带来的后果都是有意为之:
- 重启后所有表单变为 expired;重新申请链接;
- 过期表单、已用链接和已取消表单在 TTL 过后仍可应答一小时,之后惰性清扫将其丢弃,token 变为未知;
- 两个 DSH 进程不共享表单,一个进程创建的链接另一个进程不认识;
- secret_list 只显示当前进程的表单。
开发
npm test # node --test, no dependencies
npm pack --dry-run # what would ship
测试套件用桩宿主驱动真实的 apply():工具定义按真实智能体的调用方式捕获,路由处理器用 node:stream 请求替身调用,因此不打开任何端口。TTL 用例用 node:test 的 mock 定时器拨动时钟,而不是真的等待。
限制
- Node >= 22,零运行时依赖。
- 页面只由 Web GUI 宿主提供;没有 webServer 的无头组合无法使用本插件。
- label 为 2..5 个词,description 为 300 个字符:表单是索取单个值的提示词,不是第二个聊天窗口。
- kind: 'single' 会去掉内嵌换行,粘贴密钥文件必须用 kind: 'multi'。
许可证
MIT——见 LICENSE。
单行字段为什么是 textarea
Firefox 会在 input[type=password] 上弹出密码管理器,而在文本输入框上它按启发式规则判断为登录字段(Telegram 机器人卡片在群组引用上遇到的也是同一个问题)。textarea 永远不会被当作登录字段,所以单行表单改用只有一行的 textarea;值靠一层圆点覆盖层隐藏,而不是靠元素类型隐藏,这样显示切换在每个浏览器里都能正常工作,包括不支持 -webkit-text-security 的浏览器。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
unera/dsh-secret-form
通过一次性网页链接安全传递密钥,避免密钥进入聊天记录
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,399未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,384已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13950 个插件含 PR 收录与…
★ 360已汉化DSH
71.9
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。