dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness (DSH) 官方标准双半侧扩展插件:为外部机器人(MaiBot / 飞书 / 微信)及 CI/CD 系统提供开箱即用的轻量 HTTP POST / RESTful 任务调度、会话管理与 MCP 工具调用网关。
DeepSeek Harness (DSH) 双半侧统一网关与鉴权桥梁插件
融合 人类端多用户安全登录 + 机器端轻量 HTTP POST / RESTful API 调度网关。
⭐ 主要目的之一:让 DSH 支持公网访问——用账号密码自助换取官方动态凭证,替代"登录服务器抓 token"(见「🎯 核心目的之一」)。
⚠️ 文档时效说明(20260923 核对):本文部分章节写于 DSH 0.1.2 时代,
其中「需修改 DSH 源码加 requireSession 扩展点」的路径已不适用于 0.1.5+——官方已在client/connection内原生提供browserAuth.isAuthenticated()门禁,
无需再打源码补丁。改动细节见下方「🔐 公网账号系统」章节的现状标注。
阅读时以带 ✅/⚠️ 标注的现状说明为准。
⚠️ npm 上的版本已滞后:registry 上最新为 `0.1.0`,而本仓库当前已是 `0.6.6`。
请勿以 npm 版本为准——0.1.0缺少sessionAuth服务接线,且缺少其后的全部安全加固
(会话指纹绑定、step-up 提权、伪回环绕过修复、机器通道 fail-closed 等)。
以本仓库为准(当前 `0.6.7`)。
✅ 推荐安装方式:从本仓库源码安装
```bash
dsh plugin --profile web add git+https://github.com/ptrel1/dsh-postapi-bridge.git
`🔗 npm 页面(版本较旧,仅供参考):https://www.npmjs.com/package/dsh-postapi-bridge
present 过的交付物」签发有时效、绑用户的下载地址。客户端(含安卓)用普通 GET 即可拉文件,不必再组合 Cookie/Bearer。
接口 · 方法 · 鉴权 · 说明 /api/dsh/v1/file-ticket · POST · 已登录(插件会话) · 为一条 present 记录签发临时下载票据 /api/dsh/v1/file/ · GET · 已登录 + 票据绑定本人 · 流式下载,支持 Range 续传
签发 file-ticket ──► 「待激活」,距 issuedAt < 48h 内有效
│
第一次下载请求 ──► 激活:activatedAt = now,此后可用到 now + 5h
│
激活后 5h 内 ──► 可任意下载(含 Range 续传、可重复请求,无次数限制)
now >= activatedAt + 5h ──► 410
未激活 且 now >= issuedAt + 48h ──► 410
过期后 ──► 重新调 file-ticket 拿新票(唯一续期方式)⚠️ 过期的是「票据」,源文件永远不删不改。
交付物是模型产出的工作区文件,生命周期属于用户/工作区;本插件只签发访问凭证,
无权也不该在任何过期路径上删除别人目录里的文件。
{ "sessionId": "...", "seq": 7, "index": 0 }(seq = deliverables/presented 事件的 seq,index = 该事件 files 数组下标, 与官方前端 presentedFileUrl(sessionId, seq, index) 同一套坐标)。
{
"ok": true,
"url": "/api/dsh/v1/file/<token>",
"issuedAt": 1730000000000,
"activateDeadline": 1730172800000,
"expiresAt": 1730172800000,
"activateWindowHours": 48,
"sessionHours": 5,
"filename": "报告.docx",
"size": 12345
}⚠️ `expiresAt` 语义 = 待激活截止时刻,不是下载有效期。
真正的下载有效期从首次下载起算 sessionHours(默认 5h),签发时无法预知,因此不可能提前写进响应。expiresAt 仅为向后兼容保留的歧义字段,新客户端请读activateDeadline+sessionHours,忽略expiresAt(计划 0.10.x 移除)。
🔒 响应体不返回 `ticketId`。 它是服务端状态库的键,属于内部实现细节;
对外暴露会让攻击者能探测「某张票是否存在」,也会把内部键变成对外的兼容契约。
客户端只需要 url。GET 该 url。410 票据过期或不存在;404 文件不存在;413 超限
Accept-Ranges: bytes;Content-Disposition: attachment; filename*=UTF-8''(中文名已编码)Range: bytes=0-9 → 206 + Content-Range;越界 → 416 + Content-Range: bytes */{ v, tid, sid, seq, index, uid } 的 base64url + HMAC-SHA256,secret 与session cookie 同源,校验用 timingSafeEqual。
时效一律查服务端状态。
sid/seq/index/uid 是为了验签后不查库就能做前置校验,但**不作为最终授权依据**(最终以状态库 + 登录态为准)。
tid(16 hex 随机)是状态库的键,与 token 分离:日志里可以安全地记 tid 排查,但 tid 本身不能用于下载(仍需签名 token)。
——工作区内可能存在指向外部的符号链接(模型自己就能建),前缀判断会把 /cwd/link -> /etc/passwd 误判为「在工作区内」。另校验必须是普通文件。
$DSH_HOME/data/dsh-postapi-bridge/file-tickets.json(与 access-log 同目录){
"v": 1,
"tickets": {
"a1b2c3d4e5f60718": {
"sid": "sess-abc", "seq": 7, "index": 0, "uid": "alice",
"issuedAt": 1730000000000, "activatedAt": null
}
}
}load() 惰性读(mtime 变化时重读,支持多实例共享)writeFileSync(tmp) + renameSync 原子替换;写失败只记日志,不抛错cleanup(now):插件启动时一次 + 每次签发时一次(惰性,无需定时器)。只删过期票据记录,绝不触碰源文件。
await,因此「判空 → 赋值 → 落盘」在进程内天然原子,保证只允许一次激活、后到者沿用已存在的值。这是对运行时模型的显式依赖, 已在代码注释中标明:若将来改成异步落盘,必须重新引入互斥。
生命周期事件,与「谁在用」无关。若把激活挪到鉴权之后,未登录探测就能反复刷新 48h 计时 —— 攻击者在第 47h59m 匿名探一下就能把等待期再续下去,票据永远不过期。 现在「第一次被访问即激活」把票据生命周期锁死在首次访问那一刻,这是正确性保证: 泄露出去的票被别人先激活,原主仍能在同一 5h 窗口内使用(窗口共享,不额外损失), 但它绝不会因此获得额外的 48h 延期。
file-ticket-issued(tid/sid/seq/index/uid/filename/size)、file-ticket-activated(tid/uid/issuedAt/activatedAt/sessionDeadline)、 file-download(落 finish/close,记 bytes/range/activatedAt)、 file-download-denied(带 reason)。全程不记 token 本体;审计里的 subPath 对 /file/ 脱敏为 /file/。
ctx.sessionQuery.readEvent(与官方 present-open 路由同一通道,支持冷读持久化会话)。宿主未装配该服务时 端点回 503(fail-closed)。
配置项(新增,均有默认值;非正数一律回退默认):
键 · 默认 · 说明 downloadActivateWindowHours · 48 · 票据「待激活」上限,自 issuedAt 起算 downloadSessionHours · 5 · 首次下载后的可用窗口,自 activatedAt 起算 downloadMaxMB · 200 · 单文件下载上限,超过则签发阶段即 413
package.json 与 AUDIT_VERSION 同步为 0.9.0。这个在聊天框里发的是 dsh 自带的那个我打不开,你不能做一个卡片然后卡片点击就是直接的下载地址吗
c 然后加上下载地址时效就是我想要的功能
痛点:DSH 官方交付物文件卡片在用户的手机 WebView 里点了没反应,用户拿不到文件。 方案 C:插件自己渲染一张卡片,卡片本身就是直接的下载链接(``), 点击即下载,不经过任何 POST 签票。
GET /dl/ —— 直链下载payload = base64url({ v: 2, sid, seq, index, uid, exp })
token = payload + '.' + base64url(HMAC-SHA256(secret, payload))与既有 file-ticket(语义一个字节都没改)的分工:
· file-ticket(保留不动) · dl/(本版新增) 有效期 · 48h 待激活 + 首下起 5h 窗口 · 纯时间窗,签发即开始计时 激活 · 首次下载激活 · 不激活 次数 · 不限 · 不限 服务端状态 · 有(file-tickets.json) · 无(exp 在 token 里) 用途 · 复杂分享 · 卡片直链,点击即下
/files 一致);token 里的 `uid` 必须等于当前会话用户。无 cookie → 401;uid 不符 → 403;签名错 → 403;过期 → `410`(文案明确说明过期);文件已删除 → 404。
Content-Disposition: attachment; filename*=UTF-8''…(RFC5987)+ 支持 Range(复用 parseRangeHeader)。为什么 dl 票把 `exp` 写进 token,而 file-ticket 不写:
file-ticket 的窗口从「第一次下载」起算,签发时无法预知激活时刻,只能存服务端状态;