Repository navigation
Releases
v0.7.1
0.7.1 — 修复 mux 跨用户泄漏(Issue #6,两个方向)
安全修复(使用 0.7.0 的部署请尽快升级)
- 下行(宿主 → 客户端):宿主把 mux 通道当「单用户」用,会把全部工作区与会话推给每个连接。网关现在在本插件内透明代理
/api/remote.mux,并逐帧调用策略层policy.frame()按登录归属裁决:他人的baseline.items(初始全量,最易漏)与upsert.workspace丢弃、order.workspaceIds/archivedSessionIds裁剪、session/control基线按属主投影、api-session/*等事件按会话归属;未知流 fail-closed 丢弃;被隐藏的 waterfall 接收方由代理代其释放投递。 - 上行(客户端 → 宿主):宿主对任何
sessionId的open都照单服务,因此每个open先按归属授权,未授权不转发(只向客户端回auth/forbidden)。 $events/result(审批/提问应答)删除「形状非空即放行」退档:必须能证明本登录被投递过该eventId,否则 403,fail-closed。
实测 A/B(同一构建,仅切换 DSH_AUTH_MUX_PROXY)
| 普通用户可见工作区 | 对他人会话的 open |
|
|---|---|---|
| 直通(排障退避) | 20(泄漏) | 到达宿主 |
| 代理(默认) | 0 | 我方 auth/forbidden |
| 管理员(阳性对照) | 20 | 到达宿主 |
其他
- 删除 v0.7.0 起已不可达的自研 mux 死代码(
typertGateway.wireStream自建流、MAX_STREAMS、StreamWork、sockets);升级入口收敛为handleUpgrade,其他升级路径重新纳入upgrade策略(默认仅管理员,uiAuth.registerPolicy()可放开)。 - 修正安全套件分类汇总位置(此前
I18N/WS-ISO与 v0.7.0 各面在表格里恒显示0/0);现 164/164 通过、17 个分类。 - 修正
fieldOf()漏看value一层——宿主把eventId放在value.eventId,此前$events/result的严格属主绑定从未匹配到过。
验证边界(如实说明,勿当作全覆盖)
- 本环境宿主运行时对 remote 建出的会话回
session not found,因此「他人会话内容」这一层无法实证;被证实的是「未授权的open不再被转发」。 emit/api-session/*帧在本环境从不投递(实测连管理员也为 0 帧)⇒ 事件面的过滤有单测与代理接线断言,但没有活体证据。- 排障退避
DSH_AUTH_MUX_PROXY=0会重现上述问题,仅用于定位。 - 帧级深层协议攻击仍未逐帧验证。
完整变更与回归清单见 CHANGELOG.md。
v0.7.0
目标宿主:DSH
>=0.2.0-rc.2 <0.3.0(web profile)。v0.7.0 起只支持 0.2.0 线。
升级前请阅读下方的破坏性变更;安全模型见 docs/SECURITY.md。
0.7.0 — 只支持 DSH 0.2.0 线;模型与 API Key 按用户隔离
破坏性变更
- 完全移除 legacy 传输线(DSH
0.1.1-rc.2的 dotted/api/<a>.<b>与apiProxy事件流)。
缺少connection.authorizeIndex时不降级:插件装上"明确拒绝"监听器对(HTTP 503 + 可操作文案、
WS 直接销毁),面板不可访问并打印明确错误——宁可不可访问,也不无门暴露。 - 模型与 API Key 按用户隔离(详见 RBAC 设计):
- 每个用户拥有只属于自己的 provider/模型/API Key;私有配置用
PBKDF2(登录口令) → KEK → AES-256-GCM
信封加密(每配置随机 DEK,AAD 绑定uid/profileId),会话只持有 32 字节 userKek、不持口令,
密钥不写入宿主明文凭据存储; - 管理员在任何情况下都不能读取他人(含其他管理员)的私有配置(跨用户读取是代码级拒绝);
- 管理员可把自己的模型分享给指定用户:被授权者可用、可看余额、看不到 Key,撤销立即 fail-closed;
- 未配置即阻断(Q2):既无自有配置也无分享的用户,模型目录为空、切换模型被拒,不回退部署级配置;
- 余额改由插件服务端代查(
balanceQuery,只回数字;60s 缓存 + 限流);原生账户页对普通用户不可用(Q7)。
- 每个用户拥有只属于自己的 provider/模型/API Key;私有配置用
- 端点面按 DSH 0.2.0 重登:
pluginManager只读放行/写拒绝、permissionPresets/catalog放行、
userQuestions按会话、officeToPdf需 session+workspace+user 三重归属、account/*全拒绝;
自家/auth/*改为官方路由注册(webServer.register),最外层闸门保留(0.2.0 无全局前置钩子)。 - 兼容声明修正:旧的
>=0.1.1-rc.2 <0.2.0在 npm 预发布匹配规则下只匹配0.1.1-rc.2;
现为>=0.2.0-rc.2 <0.3.0+ 逐版本dshReleases(见 0.2.0 兼容性)。
按用户模型的调用路径(R1-ii)
- 每条自有配置与对外分享的配置注册一条宿主 provider 路由(
ui-auth-<id>):认证头x-api-key
由我们的resolveAuth在调用瞬间解密(私有→会话 KEK;分享→服务端主密钥),
因此既不必重写协议实现,也不把用户 Key 落到宿主明文存储。 - 说明:
llm/streamwaterfall 对 agent-loop 请求深度冻结且只读,故不采用"在 waterfall 内改写凭据"的路线。
界面
- 普通用户的【模型与密钥】页:解锁、列出自己的配置与收到的分享、设默认、选用分享、删除、查余额、新增配置;
- 管理员新增【分享管理】页:创建分享、按用户名授予/撤销、查看每条分享的用量;
- 原生账户页对普通用户替换为说明;移除了隐藏出厂导航行的 nth-child hack(0.2.0 由 settings-shell 掌管导航)。
在真实实例上验收时发现并修复
- 凭据键语法(严重):宿主凭据记录要求恰好两段
<scope>/<id>,此前写成三段会让宿主的凭据服务在
下次启动时拒绝解析整份文档,导致依赖凭据的插件全部等待、面板起不来。已改为两段并加写入前守卫与回归测试。 - 按用户模型的调用路径(R1-ii):三处修复——宿主管包要从宿主进程入口解析(profile 里没有这些包)、
ctx.llm需要作用域注入、连接事实要由官方 schema 物化。修复后实测:普通用户的llm/listProviders
出现自己的ui-auth-<id>路由,模型目录被精确裁剪(R2)。 - 查余额一直 unavailable:余额 fetcher 定义了却从未注入
ProfileService。修复后实测真 Key →CNY 5.78。 - 设置页一直「重新连接中」:网关
gateUp接管 WebSocket 升级并交给自研的 0.1.5 时代 mux,
与 0.2.0 客户端协议不兼容 → 连接反复重建(管理员与普通用户都受影响)。现改为校验会话后把升级请求交给宿主。
实测管理员与普通用户均不再重连,初始化正常继续。
验收阶段(真实 0.2.0 实例)追加修复
- 跨实例互相踢下线:会话 Cookie 名按
DSH_HOME唯一化(Cookie 不区分端口)。 - 查余额:新增批量接口
balanceQueryAll(请求内顺序查询、整批节流一次);锁定时明确显示「需先解锁」。 - 设置页一直「重新连接中」:升级请求不再交给自研 mux,改为会话校验后转交宿主。
- 模型页排版:表格化 + 单组按钮 + 口令框独占一排(75% 高度)+「解锁」与其它按钮同排 + 「校验有效性」。
- 修改密码 / 注册页:当前密码失焦校验(绿/红 + 2px 荧光)、新密码三档强度、确认框门控与实时比对。
- 凭据键语法:两段
<scope>/<id>(三段会让宿主下次启动失败),加写入前守卫与回归测试。 - R1-ii 接线:宿主管包从宿主进程入口解析、
ctx.llm作用域注入、连接事实由官方 schema 物化。
多语言(i18n,中/英)
- 跟随 DSH 的 locale 服务自动切换:客户端
ctx.locale.register/bind+ 订阅locale/change;
服务端渲染页按Accept-Language(DSH 在无显式偏好时也是交给浏览器);错误消息走同一词典。 - 词典两层:语义键(
t())+ 约 320 条短语表(中文原文 → 英文,子串替换,支持拼装式文案);
长键优先、未收录保持原文、只作用于本插件自己的面板且可双向还原。
验证
| 项目 | 结果 |
|---|---|
真实 DSH 0.2.0-rc.2 实例实测(npm run test:compat:0.2.0) |
38/38 |
npm test(含 security-suite 158/159、host-smoke、login-page-check、客户端契约自检) |
全绿 |
| 策略 / 加密 / 信封 / 服务 / 路由 单测 | 19/19 · 7/7 · 5/5 · 7/7 · 4/4 |
npm run store:check |
20 passed / 0 failed |
已知边界(矩阵已全测):0.2.0-rc.2 / 0.2.0-rc.1 / 0.1.7-rc.2 / 0.1.7-rc.1 均 38/38;
0.1.6-alpha.2 37/38、0.1.5-rc.3 / 0.1.5-rc.2 / 0.1.5-rc.1 均 34/38——失败项全部是这些宿主
不存在的端点(pluginManager/*、permissionPresets/catalog → 404),属能力收窄而非回归;
0.1.6-alpha.1 无法评估:该版本宿主自身启动失败(上游缺陷:@deepseek-ai/dsh-app-boot
未导出 bin.js 需要的 watchUserPatches),与本插件无关。
qrcode 依赖本次未改动(按所有者要求)。
v0.6.5
dsh-ui-auth v0.6.5
修复 modern 线(DSH 0.1.2+)上普通用户两个设置页加载失败的问题。
修复
1. 【Agent 预设】显示「无法加载 Agent 预设」
现象:普通用户打开【设置】→【Agent 预设】显示「无法加载 Agent 预设。」,
客户端日志为 client api: agentPresets/list failed: transport failure for /api/agentPresets/list: HTTP 403。
原因:modern 路径对普通用户是 deny-by-default,而 agentPresets/* 被整体拒绝;该页在加载时
先取预设清单(新建会话的预设选择器同样依赖它),一处被拒即整页失败。被拒的是只读元数据,
不是需要保护的写操作——属于白名单过窄的误伤。
2. 【插件】显示「暂时无法读取插件」
同一类问题的第二处:该页只调用 pluginInventory/list,也被整体拒绝。
修复后的权限边界
| 端点 | 普通用户 |
|---|---|
agentPresets/list(预设清单) |
✅ 放行 |
agentPresets/read、agentPresets/select(本会话所用预设 / 为会话选择预设) |
✅ 放行,按会话属主校验 |
pluginInventory/list(已安装插件清单) |
✅ 放行(只读) |
agentPresets/copy、agentPresets/deletePreset |
❌ 仅管理员(预设可挂载插件与提示词,创建预设属提权面) |
pluginInventory/install、uninstall、enable、disable、update |
❌ 仅管理员(安装/卸载/启停插件属部署方能力) |
插件设置与密钥(settings/*、credentials/*) |
❌ 仅管理员 |
白名单是按端点逐条登记的,因此宿主后续新增的插件管理类写操作默认仍然被拒绝。
legacy 线(0.1.1-rc.2)不受影响(该线本就只拦管理员面)。
验证
| 验证项 | 结果 |
|---|---|
普通用户设置页验收(HTTP + 真实浏览器,新增 test/live-user-pages-check.mjs) |
24/24 |
| modern 策略单测 | 15/15 |
隔离 DSH 0.1.5-rc.1:HTTP/unary 与授权面 / remote.mux 逐帧隔离 |
29/29 / 12/12 |
legacy 线审计(真实 0.1.1-rc.2 部署,普通用户,浏览器 + HTTP) |
16/16(两页均正常渲染,无同类问题) |
npm test、typecheck、store:check、verify:clean |
全绿(147/147 + 各冒烟)/ 通过 / 20/20 / 通过 |
文档
- README:新增「普通用户能看到什么」权限表;顶部加入 legacy(DSH 0.1.1-rc.2)支持将于 v0.7.0 起结束
的提示(如 DSH 架构有大变动会提前声明结束时间),并补上 npm 版本与下载量徽章。 - 兼容性文档:补全普通用户可达面/拒绝面清单,并新增「已知边界」表——把仍有意收紧的设置项
(模型与密钥、插件设置、预设创作、目录打开、设置写入、斜杠命令等)逐条列明。 - 界面预览图在全新一次性实例上重拍。
升级提示:升级后重启面板即可,无需数据迁移;legacy 线行为不变。
v0.6.4
dsh-ui-auth v0.6.4 — 通行密钥(Passkey)登录
两步验证不再只等于 TOTP:这一版可以用指纹 / 面容 / 设备 PIN 代替密码登录,一个账号可绑定
多个通行密钥(本机密钥 + 手机密钥),并且不输入用户名就能登录。
新增:通行密钥(Passkey / WebAuthn)
- 多设备、多密钥:每账号最多 20 个,可分别命名;两个添加入口——「本机通行密钥」用平台认证器
(Windows Hello / Touch ID),「手机扫码添加」由浏览器自身显示跨设备二维码,手机无需与面板同网。 - 免用户名登录:使用可发现凭据(
residentKey: required),登录页一键「🔑 使用通行密钥登录」。 - 协议交给成熟社区库:服务端
@simplewebauthn/server,浏览器端@simplewebauthn/browser
(登录页与设置面板共用同一份 bundle),不自行实现 base64url / CBOR / clientDataJSON 等细节。 - 管理界面:设置面板新增「通行密钥(Passkey)」卡片(名称、类型、最近使用、备份状态、
重命名、删除);管理员用户表格新增「通行密钥」列与「清除通行密钥」(设备丢失救援)。
变更:登录矩阵定稿(破坏性)
- 移除「免密 + 动态码」登录:只提交动态码、不提交密码的登录请求一律返回 400,且不再区分
账号是否启用 TOTP(避免账号状态枚举)。免密入口统一收敛到通行密钥。
| 账号状态 | 可用的登录方式 |
|---|---|
| 两步验证关闭 | 用户名 + 密码;通行密钥 |
| 两步验证开启,已绑定 TOTP | 用户名 + 密码 + 动态码;通行密钥 |
| 两步验证开启,只绑定了通行密钥 | 用户名 + 密码 + 通行密钥;通行密钥(可免用户名) |
- 反锁死不变量:
twoFactor只在账号仍保有至少一种因子时为真;开启两步验证前必须先绑定因子;
移除最后一个因子时自动关闭两步验证;两步验证开启且仅剩一个通行密钥时拒绝删除该密钥。
该不变量在凭据库的读取、创建、修改三个边界统一规范化,手工改坏的记录也会被修正。
安全设计要点
- 用户验证(
userVerification: required)+ 证明书策略attestation: none; - 挑战一次性、5 分钟过期、内存中限定条数,绑定用途与账号;严格校验来源(origin)与 RP ID;
- 签名计数器回退检出并拒绝(认证器被复制的迹象);
- 改动登录因子必须二次验证(密码 + 动态码,或密码 + 一次已有通行密钥断言),服务端签发
5 分钟、绑定账号与来源 IP 的一次性票据——仅窃取会话不足以添加或删除登录手段; - 私钥永不离开用户设备,服务器只保存公钥与公开元数据;通行密钥相关操作全部写入审计日志。
已知限制:通行密钥对访问地址有硬性要求
浏览器不接受 IP 字面量作为通行密钥域(RP ID)——实测 Chrome 152 会抛
SecurityError: 127.0.0.1 is an invalid domain。因此请用 http://localhost:<端口> 或
域名 + HTTPS 访问面板;插件会在不可用地址上提前返回 409 并在界面给出应改用的地址,
不会出现"按钮点了没反应"。密码与 TOTP 登录不受影响。
验证(两条宿主线均以 0.6.4 构建实测)
| 验证项 | 结果 |
|---|---|
npm test(构建 + 全链) |
安全套件 147/147、modern 策略 13/13、host-smoke(含通行密钥 16 项)、client-smoke(含 9 项)、登录页/端点联通 24/24 |
通行密钥端到端(隔离 DSH 0.1.5-rc.1 + 真实 Chrome + CDP 虚拟认证器) |
27/27 |
隔离 DSH 0.1.5-rc.1:HTTP/unary 与授权面 / remote.mux 逐帧隔离 / 浏览器级 |
28/28 / 12/12 / 8/8 |
真实 DSH 0.1.1-rc.2:legacy 回归 / 浏览器级(127.0.0.1 源、localhost 源) |
14/14 / 7/7 / 7/7 |
| 干净一次性环境下的安装 / 冷启动 / 卸载(含新增运行依赖) | 通过(/auth/passkey/login/options → 409 + ip-literal,卸载后路由 404/405) |
npm run store:check / npm run verify:clean |
20/20 / 通过 |
其他
- 新增登录页脚本端点
GET /auth/passkey/browser.js(IIFE bundle,带 ETag,匿名可取,无秘密)。 - 新增可选环境变量
DSH_AUTH_RP_ID/DSH_AUTH_ORIGIN/DSH_AUTH_RP_NAME。 - 注册成功引导页与登录提醒弹窗改为同时介绍两种第二因子(TOTP 或通行密钥)。
- 文档:SECURITY.md 的测试矩阵改由测试自身生成;新增
npm run docs:matrix与npm run assets
两个维护命令。
升级提示:从 0.6.2 及更早版本升级后,原先依赖「免密 + 动态码」登录的用户需要改用密码登录
(或用通行密钥);其余数据不受影响,通行密钥与 TOTP 均是新增/可选配置。
完整变更见 CHANGELOG.md。
dsh-ui-auth v0.6.2
dsh-ui-auth v0.6.2 — TypeScript 重写(行为不变)
本版本把插件改为 TypeScript 源码 + 构建产物:src/*.ts 是唯一源码,lib/*.js 由构建生成
(DSH 直接消费仓库,因此产物继续提交)。这是一次语言层面的重写——核心流程、UI 文案、CSS、
端点策略与错误文案全部保持不变。
目录与构建
| 源码 | 产物 | 构建 |
|---|---|---|
src/index.ts(宿主:网关/认证/用户管理/审计) |
lib/index.js(ESM) |
tsc -p tsconfig.json |
src/modern-gateway.ts(0.1.2+ 传输适配) |
lib/modern-gateway.js |
同上 |
src/modern-policy.ts(端点策略表) |
lib/modern-policy.js |
同上 |
src/client.ts(设置面板客户端) |
lib/client.js |
node build/client.mjs(esbuild) |
客户端为什么用 esbuild 包装:DSH 客户端模块契约是经典脚本 + 工厂形式 ——
window.__ModuleLoader__.load({ id, factory: (require) => … return module.exports }),
且 require('react') 取自宿主冻结模块表(React 不是全局变量)。build/client.mjs 复刻该包装并在
构建后自检产物形状;charset: 'utf8' 保证中文文案不被转义成 \uXXXX。
npm ci # 需要 devDependencies(typescript / esbuild / @types/*)
npm run typecheck # 宿主 + 客户端两套 tsconfig 的严格类型检查
npm run build # src/*.ts → lib/*.js
npm test # 构建 + 全链测试
npm run verify:clean # 校验 lib/ 与 src/ 同步(CI 使用)安装期不需要构建:lib/*.js 已提交,dsh plugin add 直接消费产物;新增的
typescript/esbuild/@types/* 均为 devDependencies,不进运行产物。
保真证据
逐行 diff 无意义(tsc 会重排格式、esbuild 会重打印),因此用可判定的证据:
- 新增
test/port-fidelity.mjs:对比重写前后的可观察要素多重集(字符串字面量、数字字面量、
导出名)。lib/index.js、lib/modern-gateway.js、lib/modern-policy.js完全一致;
lib/client.js仅剩注释被剥离与undefined→void 0的打印器差异。 - 宿主主体:手写版与新构建的
lib/index.js经 esbuild 压缩后 sha256 完全相同
(token 级一致,仅格式与类型擦除差异)。 - 客户端:两份 bundle 经同一 esbuild 规范化重打印后逐字节相同(等价于 AST 相同)。
- 我在移植过程中引入的两处纯结构性改写(早退守卫、局部变量)已按"核心流程不动"改回原实现。
验证(2026-09-14)
| 验证项 | 结果 |
|---|---|
npm test(构建 + 全链) |
安全套件 147/147、modern 策略 13/13、host-smoke 21 场景、client-smoke、crypto/TOTP 向量 |
隔离 DSH 0.1.5-rc.1 实例:HTTP/unary |
28/28(登录门、carrier 桥接、跨用户隔离、deny-by-default、登出吊销) |
隔离 DSH 0.1.5-rc.1 实例:/api/remote.mux 流 |
12/12(逐帧隔离、waterfall 不投递) |
隔离 DSH 0.1.5-rc.1 实例:浏览器级 |
6/6(设置面板「用户管理」入口与页面) |
真实 DSH 0.1.1-rc.2 部署:legacy 回归 |
14/14(dotted/apiProxy 路径不变) |
真实 DSH 0.1.1-rc.2 部署:浏览器级 |
5/5 |
npm run store:check |
20/20(权限信号集合不变:files/network/credentials) |
升级说明
- 无行为变更,直接升级即可;升级后重启面板(宿主半边为启动时加载)。
- 仅审阅代码的人请注意:以
src/*.ts为准,lib/*.js是构建产物;CI 会校验两者同步。
工程变更
- CI(
.github/workflows/npm-publish.yml):typecheck→build+ 全链测试 → 校验lib/与src/
一致;PUPPETEER_SKIP_DOWNLOAD=true避免在 CI 下载 Chromium;发布路径不变(Release 触发 + 每日同步)。 - 新增
.gitattributes统一 LF,保证上述同步校验在 Windows 与 Linux 上结果一致。
dsh-ui-auth v0.6.1
dsh-ui-auth v0.6.1 — 修复:DSH 0.1.2+ 上设置面板「用户管理」入口消失
这是 0.6.0 的回归修复,建议所有在 DSH 0.1.2 及以上版本使用本插件的部署升级。
现象
在 DSH 0.1.5-rc.1 实例中,0.6.0 的登录门、按用户隔离、原生 UI 都正常,但设置对话框的导航里没有「用户管理」——客户端插件什么都没注册,且没有任何报错(静默失效)。
根因
- DSH 0.1.5 把
slots服务的提供方从@deepseek-ai/dsh-client-runtime改为@deepseek-ai/dsh-client-ui-renderer; - 同时把客户端模块的到达顺序变成显式依赖(
dsh.client.inject从信息性元数据变为工厂到达屏障); - 本插件的客户端行没有声明任何依赖(boot 图为
{"id":"dsh-ui-auth","url":...},无inject字段),因此可能在slots就绪前apply; - 旧实现在
ctx.get('slots') === undefined时直接return,设置项从未注册。
该故障无法被 HTTP/RPC 级验收发现——只有真实浏览器能暴露。
修复
- 客户端插件显式声明
exports.inject = ['slots'],由宿主保证服务就绪后再apply; - 保留有界重试(40 × 250ms)作为兜底,并在最终仍取不到服务时
console.error('[dsh-ui-auth] slots 服务不可用:设置面板「用户管理」未能注册')——不再静默; - 新增浏览器级验收脚本
test/live-ui-check.mjs(npm run test:ui):真实 Chromium 登录 →
打开设置 → 断言导航含「用户管理」、页面渲染「我的账号 / 修改密码 / 两步验证」、
管理员额外看到「创建用户 / 邀请码管理」、且无插件级错误日志;触发器兼容两种标注
(0.1.5 为aria-label="设置",0.1.1-rc.2 为按钮文本「设置」)。
验证(2026-09-14)
| 环境 | 结果 |
|---|---|
隔离 DSH 0.1.5-rc.1 实例(admin) |
设置导航 [通用设置 | 模型 | 插件 | Agent 预设 | 用户管理],页面渲染正常,管理员额外项正常,无插件级错误 —— 6/6 |
真实 DSH 0.1.1-rc.2 面板(test1) |
「用户管理」导航与页面同样正常 —— 5/5(确认未影响旧宿主) |
npm test |
security-suite 147/147 + modern 策略 13/13 |
升级方式
dsh plugin --profile web add dsh-ui-auth # 或按你现有的 link 安装方式
# 重启面板生效(Host 半边为启动时加载;客户端模块刷新页面即可)纪律变更
客户端注册类改动必须用真实浏览器验收(npm run test:ui),不能只用 HTTP/RPC 断言——
这条已写入 docs/DSH-0.1.5-COMPATIBILITY.md 第 5 节「已知问题与修复记录」。
dsh-ui-auth v0.6.0
dsh-ui-auth v0.6.0 — DSH 0.1.5 适配(双线支持)
同一份代码按能力探测自动选择传输适配:ctx.get('connection')?.authorizeIndex 存在走 modern,否则走 legacy。无需任何配置。
| Host 版本 | 路径 | 状态 |
|---|---|---|
0.1.1-rc.2(开发基线) |
legacy:dotted /api/<a>.<b> + apiProxy 事件流 |
行为完全不变 |
0.1.2-rc.1 … 0.1.5-rc.1(当前 npm latest) |
modern:slash Remote + /api/remote.mux + 原生浏览器 cookie 门 |
新增 |
1. 为什么需要适配(0.1.5 相对基线的真实变化)
apiProxy服务整体移除,/api/events.mux、/api/events.host不再存在;- RPC 改为 slash 命名(
session.list→session/list)并新增大量端点——旧的 dotted 方法表会命中"未知端点默认放行"分支,造成登录门仍在但按用户隔离静默失效; - 流式传输合并为一条 mux(
open/cancel/item/end/error),转发事件改为$events逻辑流 +POST /api/$events/result回执; - 新增原生浏览器 cookie 门:浏览器必须持有
dsh-auth-<hash>签名 cookie(由启动令牌换取),插件登录本身不足以让 UI 可用; - 存在绕过 Remote 的
/api精确路由(session.export、session/uploadFileBinary、file、present.*)与/open-in-app/*。
2. modern 路径做了什么
- carrier 桥接:对每个已登录请求,进程内调用
connection.authenticatedUrl()+authorizeIndex()换取原生 cookie 并注入转发请求,剥离客户端自带dsh-auth-*,随后connection.requestRejection()复核。浏览器既不持有原生 cookie,也不持有进程启动令牌。 - deny-by-default 策略:按 0.1.5 实际端点表(63 端点 / 15 命名空间)逐条授权;普通用户仅可访问自有 session / workspace / 文件范围 / agent 范围与共享只读元数据;
settings/*写、credentials/*、workspace/create、directoryPicker/*、commands/execute、subagents/*、agentPresets/*、llm/discoverModels、pluginInventory/list、dynamicCordisRunner/*、session/openWorkspacePath与未知端点一律 403。 - 归属与过滤:
session/create|fork先落盘归属再返回成功(失败回滚);session/list|search过滤 items;session/control的queues/jobs/projections三张 baseline 表与workspace/follow的 baseline/增量帧按键裁剪。 - 流式隔离:mux 每次投递前复核登录态与属主;登出/改权 1 秒内关闭长连接;被过滤的
waterfall由插件代答$events/result {kind:'next'},避免 owner 的交互永久悬置;$events/result只接受与本登录 + 本连接 + 已投递事件三者匹配的回执。 - 绕过 Remote 的路由:
/api/session.export、/api/session/uploadFileBinary按sessionId属主放行;/api/file、/api/present.*、/open-in-app/*仅管理员。 - 投影修复:需要改写响应体的请求强制上游
accept-encoding: identity,写回时清除继承的content-encoding/transfer-encoding/content-length(否则浏览器解压失败,实测中真实踩到)。
3. 其他修复与变更
- 凭据记录键合规:不合
^[a-z][a-z0-9-]*$的新用户名改用user-<sha256>记录键,并持久化退役用户名防止复用;小写用户名保持原键。该语法约束在基线即存在——旧写路径会让含大写/点用户名的注册在重启后拖垮整个凭据库(fail-closed 503),本版修复。 - 去
timer依赖:改用setInterval+ctx.effect清理,inject为['webServer','connection'](两版 Host 都提供connection服务)。注:timer在 0.1.2/0.1.5 的 base bundle 中仍存在,去掉它是可移植性改进而非激活阻断。 - 新增运行时依赖
ws@^8.21.0(/api/remote.mux的 WebSocket 实现,与 DSH 自身同线)。 - engines 对齐 DSH:
^22.19.0 || >=24.0.0。 - 兼容声明:
dsh.compatibility.dshReleases={ 0.1.1-rc.2: compatible, 0.1.5-rc.1: compatible }。
4. 升级说明
- 0.1.1-rc.2 用户:行为不变;仅新增
ws依赖(dsh plugin add会自动安装)。 - 0.1.2+ 用户:普通用户默认不可创建 workspace(路径属宿主能力,由部署方分配),且不可写任何设置命名空间;需要放开的部署可用
ctx.get('uiAuth').registerPolicy()逐条登记。
5. 测试与验证
npm test:security-suite 147/147 + modern 策略 13/13- 隔离 DSH 0.1.5-rc.1 实例(官方 npm CLI + 一次性
DSH_HOME+ profile link 安装):- HTTP/unary 28/28 — 登录门 302、未认证 API 401、登录后原生 UI 200(carrier 桥接成功)、slash Remote 可用、归属先落盘、跨用户隔离、
cwd覆盖 403、9 项管理面 403、未知端点 403、登出 401 - mux 流 12/12 — 未认证升级被拒、
$eventsready、session/controlbaseline、普通用户 baseline 裁剪为空、api-session/added逐帧隔离、普通用户不收到 waterfall
- HTTP/unary 28/28 — 登录门 302、未认证 API 401、登录后原生 UI 200(carrier 桥接成功)、slash Remote 可用、归属先落盘、跨用户隔离、
- DSH 0.1.1-rc.2 真实部署回归:14/14 — dotted Remote 仍可用、管理面 403、会话导出属主检查 403、登出吊销生效
npm run store:check:20/20 门禁(运行时权限信号仍仅 files/network/credentials)
复现脚本:test/live-015-check.mjs、test/live-015-mux.mjs、test/live-legacy-check.mjs。
6. 文档
- docs/DSH-0.1.5-COMPATIBILITY.md — 端点清单、有意收紧项、
uiAuth下游接口、实测证据 - docs/STORE-EVIDENCE.md — 依赖/权限/外部服务/失败边界声明与两个模块的运行验收
7. 致谢
现代适配层复用 PR #1(StormSeven1)的设计骨架:carrier 桥接、mux 处理、correlation 绑定、waterfall 释放语义、uiAuth seam 与策略回归用例;本版按 0.1.5 的真实端点/事件表重建,并补上其未覆盖的压缩响应投影与绕过 Remote 的 /api 路由。
dsh-ui-auth v0.5.1
dsh-ui-auth v0.5.1 — 安全加固
依据外部安全工程师审计报告核验后实施的 5 项加固:
1. Secure Cookie 动态启用(中危 → 已修)
TLS 直连或(仅信任反代时)X-Forwarded-Proto: https 的安全通道下,dsh_auth Cookie 自动追加 Secure;HTTP 内网调试不受影响;未信任反代时 XFP 不生效(防伪造)。
2. trustProxy 解析加固(低/中危 → 已修)
X-Forwarded-For 改取最右(最近受信反代追加、客户端不可伪造),修复开启 trustProxy 时伪造 XFF 可绕过登录锁定的问题;默认仍不信任 XFF。
3. bootstrap 自毁(低危 → 已修)
任意用户改密成功后自动删除 dsh-ui-auth-bootstrap.txt(明文初始密码不再长期残留)。
4. WS 未知帧 fail-closed 纪律固化
无归属的未知帧类型一律丢弃(实现本就默认拒绝),以测试断言固化,防未来新增帧类型时漏桶。
5. 会话 Token 哈希落盘(中危 → 已修)
dsh-ui-auth-sessions.json 只存 Token 的 SHA-256 哈希(64 位 hex),磁盘无明文 Token。
测试
- security-suite 147/147(新增 Secure Cookie 3 项、clientIp 取最右/伪造 XFF 3 项、WS 未知帧 1 项)
- host-smoke 场景 21 bootstrap 自毁、场景 16 会话哈希断言
- 真实部署:会话哈希 save/verify 跨重启恢复 ✓、注册面 10/10、sessions 文件全部 64-hex 哈希校验
dsh-ui-auth v0.5.0
dsh-ui-auth v0.5.0 — 用户注册(邀请码)+ TOTP 两步验证 + 2FA
新增功能
用户注册(邀请码)
/auth/register注册页:邮箱 + 用户名 + 密码(确认密码 + 眼睛图标)+ 有效邀请码- 密码策略:至少 8 位且包含两种及以上字符类型(大小写字母/数字/符号)
- 管理员在【用户管理】→【邀请码管理】生成/撤销邀请码,可见每个码的使用数/剩余数
- 注册成功自动登录并进入 TOTP 引导页(推荐立即添加两步验证令牌)
TOTP 两步验证(RFC 6238)
- 绑定二维码(
qrcode库 SVG)+ 密钥/otpauth 手动输入兜底 - 2FA 开关:绑定 TOTP 后默认不强制;开启后登录需「密码 + 动态码」,关闭时密码或动态码任选其一
- 免密 TOTP 登录(密码留空 + 动态码);动态码错误计入登录失败锁定(防爆破)
- 登录页动态码输入 + 眼睛按钮;未绑定用户登录后弹窗提醒(会话内一次,可永久忽略)
- 用户管理页 TOTP 卡片(生成/启用/移除/忽略/2FA 开关);管理员可移除任意用户令牌
UI 修复
- 输入框宽度越界(box-sizing)、角色徽章换行(nowrap)、TOTP 提醒频率(会话一次)
测试
- security-suite 141/141、host-smoke 20 场景、TOTP RFC 6238 官方向量、client 冒烟全绿
- 真实部署逐项验证:注册端点 10/10、TOTP 全链路 6/6、2FA 登录 4/4
- 0.5.0 新增面安全矩阵补测(越权/注入/状态机/部署 9 项)
dsh-ui-auth v0.4.0
dsh-ui-auth v0.4.0 - 四项改进:1) WebSocket 事件流按用户隔离(网关代理逐帧过滤,网络层零泄漏,无需反代);2) 限流配置化(DSH_AUTH_MAX_FAILS/LOCK_MS/TRUST_PROXY);3) 会话持久化(重启不掉线)+ 启动竞态修复;4) 管理员操作审计 JSONL。测试:security-suite 98/98、host-smoke 17 场景、真实部署逐项验证通过。