fix(uds-auth): emergency login workspaces, Desktop key prompts, persistent sessions
Some checks are pending
ci / test (push) Waiting to run
ci / test-postgresql (push) Waiting to run

Emergency (fallback / sealed-box) admins lost the workspace sidebar and saw
"sign-in failed / add API key" on DSH Desktop because the remote.mux opens
anonymously at boot and every early call was rejected terminally.

- gateway: anonymous workspace/follow reaches dsh-acl's empty baseline;
  anonymous streams are parked until the carrier aborts instead of failing;
  anonymous workspace/initializeDefault answers "nothing created"
- request-auth: a dead UDS_SESSION cookie no longer shadows a valid WS ticket
  or bridge (sync + async variants used by all callers)
- dsh-acl: /api/* waits (bounded 15s) for the gateway ACL during Host startup
  instead of 403, so the Desktop welcome read no longer fails
- desktop-bootstrap: key/account projection is best-effort per reference
- client: emit connection/reset after login so boot-time caches (settings
  describe mirror -> Settings > Models) re-read under the real principal
- sessions: absolute lifetime fixed at 7 days (was min(ttl, 7d) = 30 min);
  bridge expiry slides with its session; MemoryStore persisted to
  sessions.json (bearer hash only, userData AES-256-GCM sealed)
- README: session lifetime/persistence and DSH compatibility notes

Also includes previously uncommitted uds-auth 0.3.x work in this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-12 02:11:09 +08:00
parent fa4d5d6d97
commit df97c5e042
72 changed files with 8436 additions and 1192 deletions

View file

@ -0,0 +1,35 @@
dijkstrajs 1.0.3 (MIT)
```
Dijkstra path-finding functions. Adapted from the Dijkstar Python project.
Copyright (C) 2008
Wyatt Baldwin <self@wyattbaldwin.com>
All rights reserved
Licensed under the MIT license.
http://www.opensource.org/licenses/mit-license.php
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```
---
qrcode 1.5.4 (MIT)
The MIT License (MIT)
Copyright (c) 2012 Ryan Day
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

109
uds-auth/docs/sbom.cdx.json Normal file
View file

@ -0,0 +1,109 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"metadata": {
"component": {
"type": "application",
"name": "uds-auth",
"version": "0.3.11",
"bom-ref": "pkg:npm/uds-auth@0.3.11",
"licenses": [
{
"license": {
"id": "MIT"
}
}
]
},
"properties": [
{
"name": "uds-auth:bundle-sha256",
"value": "1ca76135efb5f93975793da04024409ef215d8b595d5b5194707e3acc693a15f"
},
{
"name": "uds-auth:external-peer",
"value": "React supplied by DSH; audit its exact version in the Host SBOM"
},
{
"name": "uds-auth:scope",
"value": "Actual vendored runtime bundle; development dependency audit is a separate npm audit --include=dev gate"
}
]
},
"components": [
{
"type": "library",
"name": "dijkstrajs",
"version": "1.0.3",
"purl": "pkg:npm/dijkstrajs@1.0.3",
"bom-ref": "pkg:npm/dijkstrajs@1.0.3",
"licenses": [
{
"license": {
"id": "MIT"
}
}
],
"properties": [
{
"name": "uds-auth:delivery",
"value": "bundled in lib/qrcode-bundled.cjs"
},
{
"name": "npm:integrity",
"value": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
{
"name": "uds-auth:source-files",
"value": "[{\"file\":\"dijkstrajs/dijkstra.js\",\"sha256\":\"7f5721e77332370a71314f1536b8c552f4f9dbd99bbc6c5aa10df3253eacfd5c\"}]"
}
]
},
{
"type": "library",
"name": "qrcode",
"version": "1.5.4",
"purl": "pkg:npm/qrcode@1.5.4",
"bom-ref": "pkg:npm/qrcode@1.5.4",
"licenses": [
{
"license": {
"id": "MIT"
}
}
],
"properties": [
{
"name": "uds-auth:delivery",
"value": "bundled in lib/qrcode-bundled.cjs"
},
{
"name": "npm:integrity",
"value": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="
},
{
"name": "uds-auth:source-files",
"value": "[{\"file\":\"qrcode/lib/can-promise.js\",\"sha256\":\"fd18906803bd4cfe4f23801a7032c5a1e10be31274ba25dc34541932cdd8f253\"},{\"file\":\"qrcode/lib/core/utils.js\",\"sha256\":\"1b66145cb9afddadf46ea7314b168e0b8369b2ab4938256fe64bd1c465a473bd\"},{\"file\":\"qrcode/lib/core/error-correction-level.js\",\"sha256\":\"7f31b2250223cb7549bf3c305cce948ad9b0ee67cb9ee93b6c08352b7fa2b7f6\"},{\"file\":\"qrcode/lib/core/bit-buffer.js\",\"sha256\":\"c6f982814aa373da6e44183a61f75762c8559909087fff76603885b180bac0ad\"},{\"file\":\"qrcode/lib/core/bit-matrix.js\",\"sha256\":\"77e051929fbc9d3b3ba91bd9b190f9a1ab3ccea162d627696ad2c01bf41e51bd\"},{\"file\":\"qrcode/lib/core/alignment-pattern.js\",\"sha256\":\"7e642a801c5c3d114bcceeeb08a3930369ec16fe874bdb617f02efb43aaea9ba\"},{\"file\":\"qrcode/lib/core/finder-pattern.js\",\"sha256\":\"e5808e9b8ed06fd7c3ed201a03f7e7341a5c80f6a8b4cc78c9a94c3ee0ddd6e2\"},{\"file\":\"qrcode/lib/core/mask-pattern.js\",\"sha256\":\"c8cfdab7b8d8276f9c1128ab8e5d930759df7594f03211f72b80c3002af91aa9\"},{\"file\":\"qrcode/lib/core/error-correction-code.js\",\"sha256\":\"03683cae6cc410b83f8ad0e87fa394df5102b52ca4ce843e507aa9f6acce13e5\"},{\"file\":\"qrcode/lib/core/galois-field.js\",\"sha256\":\"d487207ce5040b49f95441bedafb6dbc9c010b245a0f0f47b9e3f10f8d9530a6\"},{\"file\":\"qrcode/lib/core/polynomial.js\",\"sha256\":\"34cb4e4216fc51fe75ac1c4bb00c4f4fc118a51da50c8250aec951062ff3a3c4\"},{\"file\":\"qrcode/lib/core/reed-solomon-encoder.js\",\"sha256\":\"080273fcf5a73c2348330f3edb55f3968c78853e298dc55a074becd7cee3faa2\"},{\"file\":\"qrcode/lib/core/version-check.js\",\"sha256\":\"9cffe3a6f9cbb978022c0d0b1652fb2f3d381b9e48b7e01dfc744ded28736da2\"},{\"file\":\"qrcode/lib/core/regex.js\",\"sha256\":\"974d9358148d11c8e48713ef2da8fc12e597b76f4e6fc6bde00cfe7c8094efa1\"},{\"file\":\"qrcode/lib/core/mode.js\",\"sha256\":\"25d3574cf4b0cdbd62363ff2c57eb46da41d21124e6d37eb0e84f979799bd530\"},{\"file\":\"qrcode/lib/core/version.js\",\"sha256\":\"1221665a29e6927d3823912bde8bd711c114c61188d7ce862997a33d47fcfba9\"},{\"file\":\"qrcode/lib/core/format-info.js\",\"sha256\":\"889b133cd6a86f4079a3a5d1dfd94c495ca71d4fde4ec88ce9e9a5de358629d0\"},{\"file\":\"qrcode/lib/core/numeric-data.js\",\"sha256\":\"3e506376e4c84b88162024c1d840b5bc3721da3b0d02f18784161adea5e3b2e8\"},{\"file\":\"qrcode/lib/core/alphanumeric-data.js\",\"sha256\":\"cf8f81197e02c0db069155a2e82c098d26329019cfd06cc0e4479124636fa1ea\"},{\"file\":\"qrcode/lib/core/byte-data.js\",\"sha256\":\"24614cb942165dcc5378117a951cc1d68fbfc2e60043830111ae99283cd9aa94\"},{\"file\":\"qrcode/lib/core/kanji-data.js\",\"sha256\":\"2dcd7d5aee492392e0c47976ae718e9783c5b13234487804ed21558fbd4a62de\"},{\"file\":\"qrcode/lib/core/segments.js\",\"sha256\":\"abd08a6ddd778c92e36e2a303d2b4a3b4ad1d9089d2a7605bee8b9d06da7846a\"},{\"file\":\"qrcode/lib/core/qrcode.js\",\"sha256\":\"216e490908ed95df74992a05d44efa35ea7bc8dcab27b49b446725964704e833\"},{\"file\":\"qrcode/lib/renderer/utils.js\",\"sha256\":\"03ffb1b5f7e629bef61a83835107b391efc0b7a71ab9329a950708b20f361b87\"},{\"file\":\"qrcode/lib/renderer/canvas.js\",\"sha256\":\"0ce857661e234786295c9b4042cad6dc1f4d7a496c4990f52870d2bc4ab1e6a9\"},{\"file\":\"qrcode/lib/renderer/svg-tag.js\",\"sha256\":\"2f733e88d0b0155fae4aa4827de0fb9ae73cbbfdfc0cae52dc9d5ed17ae0447d\"},{\"file\":\"qrcode/lib/browser.js\",\"sha256\":\"868da79917b05db1f3d4402f4875148fb06ce843cbd74c65429cd6ef960b1638\"}]"
}
]
}
],
"dependencies": [
{
"ref": "pkg:npm/uds-auth@0.3.11",
"dependsOn": [
"pkg:npm/dijkstrajs@1.0.3",
"pkg:npm/qrcode@1.5.4"
]
},
{
"ref": "pkg:npm/dijkstrajs@1.0.3",
"dependsOn": []
},
{
"ref": "pkg:npm/qrcode@1.5.4",
"dependsOn": []
}
]
}

View file

@ -35,12 +35,15 @@
## Agent 环境
Shell 已注入(非密钥):
Shell / 任务 env(非密钥除非 Host 显式注入 capability):
- `DSH_SESSION_ID` — 当前 agent 会话
- `DSH_WEB_URL` — 可选,用于拼 Host 地址
- `UDS_TASK_CAPABILITY` — **推荐**:Host/`mintTaskCapability` 签发的短期令牌(scopes: `outbound` 和/或 `credentials`)
Host 在 `session/created` 时会 `stampSessionOwner(sessionId, empNo)`。Cron 可按 `ownerEmpNo` 解析。
Host 在 `session/created` 时会 `stampSessionOwner(sessionId, empNo)`。Cron 可按 `ownerEmpNo` 解析并 mint capability;**不要**依赖浏览器 Cookie。
账号停用/删除会撤销该用户全部 task capability。
## 两种合法模式
@ -49,13 +52,15 @@ Host 在 `session/created` 时会 `stampSessionOwner(sessionId, empNo)`。Cron
```http
POST /uds-auth/outbound
X-DSH-Session-Id: <DSH_SESSION_ID>
X-UDS-Task-Capability: <UDS_TASK_CAPABILITY> # scope 须含 outbound
{ "url": "https://icenterapi.zte.com.cn/...", "method": "POST", "headers": {...}, "body": {...} }
```
- 仅 **loopback**
- Host 注入 `X-Emp-No` / `X-Auth-Value`
- 剥离客户端自带鉴权头
- host 必须在白名单内
- host 必须在白名单内;URL 必须 **HTTPS**
- capability 仅 `credentials` scope 时 **不能** 调 outbound
Python(先把 `uds-skill-auth/scripts` 加入 `sys.path`):
@ -67,16 +72,20 @@ out = request("POST", url, headers={...}, body={...})
### Mode Creds(必须自管 HTTP 时)
默认 `allowRawAgentToken=false`。开启后:
```http
POST /uds-auth/agent-credentials
X-DSH-Session-Id: <DSH_SESSION_ID>
X-UDS-Task-Capability: <UDS_TASK_CAPABILITY> # scope 须含 credentials
```
返回 `{ empNo, token }`。可设进程内 `EMP_NO` / `AUTH_VALUE` 别名,**禁止**写进 SKILL.md 教模型 `printenv`。
返回 `{ empNo, token }`。可设进程内 `EMP_NO` / `AUTH_VALUE` 别名,**禁止**写进 SKILL.md 教模型 `printenv`。仅 `outbound` scope 的 capability **不能**取 raw token。
```python
from uds_skill_auth import resolve
creds = resolve() # empNo + token;默认写入 EMP_NO/AUTH_VALUE
# 优先用 request()/outbound;仅在 allowRawAgentToken=true 时使用 resolve
creds = resolve(apply_env_aliases=False)
```
Helpers 来源:插件内 skill `uds-auth/skills/uds-skill-auth/scripts/`(装到 skills 路径后,或环境变量 `UDS_AUTH_HELPERS`)。

View file

@ -12,9 +12,11 @@ uds-auth/skills/uds-skill-auth/
1. 安装 uds-auth 插件
2. 把 `uds-auth/skills/uds-skill-auth` 配进 Harness 的 skills 目录(或复制到工作区 `skills/uds-skill-auth`)
3. 用户扫码登录
3. 用户扫码登录(或由 Host 为 cron 注入 `UDS_TASK_CAPABILITY`)
4. 业务 skill 通过 sibling / skills 根找到本 skill 的 `scripts/`
推荐:**优先** `request()` / outbound;原始 `resolve()` 依赖 `allowRawAgentToken`(默认关闭)。后台任务使用 `UDS_TASK_CAPABILITY`,不要依赖浏览器 Cookie。
## 业务 skill 引用
```python
@ -33,8 +35,10 @@ def load_uds_skill_auth():
| 函数 | 用途 |
|------|------|
| `resolve()` | `POST /uds-auth/agent-credentials` → empNo+token |
| `request(method, url, ...)` | `POST /uds-auth/outbound` 出站代贴鉴权 |
| `resolve()` | 原始凭证(需显式开启 raw;可带 `UDS_TASK_CAPABILITY`) |
| `request(method, url, ...)` | `POST /uds-auth/outbound`(推荐;支持 task capability) |
| `UdsAuthError` | 未登录 / 连不上 Host 等 |
环境变量:`DSH_SESSION_ID`、`UDS_TASK_CAPABILITY`(Host `mintTaskCapability` 签发)。
完整契约:[skill-auth-standard.zh.md](./skill-auth-standard.zh.md)

View file

@ -0,0 +1,41 @@
# 升级到 0.3.10
本版修复密码并发写入、Desktop 凭据附带范围、Gateway 全局接口遗漏、扫码浏览器绑定、任务授权注入和安全状态迁移。先备份 profile 中的安全状态及密钥,再替换插件并重启 Host;不要删除 roles/owners 文件,也不要恢复历史共享默认密码。
## 登录和 Desktop
应急密码入口始终显示;未设置密码时保持禁用。已登录的授权管理员可在「账号 / 权限」设置至少 10 位的新密码。Local Admin 密封盒解锁保持独立,轮换或关闭应急密码不撤销密封盒会话。
Desktop 只把 bridge 附到 `dsh-app://app` 或 Host 启动时提供的 `__DSH_TRANSPORT__.streamBaseUrl` 对应 authority。启动信息缺失时绝不向其他本机端口外发凭据;需要支持提供这个正式 transport 字段的 DSH 版本。带 bridge 的 fetch 拒绝重定向,保留原 Request headers。一次性 WS ticket 只用于 GET `/api/remote.mux` 的真正 WebSocket upgrade;普通 HTTP、其他路径和重放均无效。构造 WebSocket 仍采用同步 XHR 取票,需真实 Desktop 验证该协议桥。
## 扫码协议变更
`qr-start` 返回 `browserBinding`,浏览器仅在内存中保存,轮询 `qr-proxy` 时通过 `X-UDS-QR-Binding` 发送。该秘密不出现在二维码、URL 或转发给 UAC 的 body 中。服务端固定发送挑战绑定的系统代码并计算 verifyCode。
当前明确支持的 UAC 契约是 outer `code.code=0000`、`bo.code=0000`,凭据来自 `other.account/empNo` 和 `other.token/authValue`;`4002` 保持等待,`1002` 表示过期。成功结果还必须通过严格工号匹配的用户资料校验。服务端在异步校验前原子预留成功兑换,直接返回 `auth` 本地会话;前端不再保存 SSO cookie,也不再依次调用 user-info 和 bridge/bind。无法识别的响应拒绝登录,不返回上游原文。外部 QR 客户端须同步适配此变更;真实 UAC 响应仍需现场核验。
## Skill、cron 和后台 shell
Host 加载 `shellEnv` 时自动注册 uds-auth contributor。每次 shell 执行根据真实 `execution.agent.session.header.id` 和服务端 owner 签发仅有 `outbound` scope 的 capability,默认 10 分钟,注入受保护的 `DSH_UDS_TASK_CAPABILITY` / `DSH_UDS_AUTH_BASE`。Python helper 优先读取这两个变量;兼容的手工 `UDS_TASK_CAPABILITY` / `UDS_AUTH_BASE` 仅在没有 Host 管理变量时使用。所有 base,包括旧 `DSH_WEB_URL`,都必须是 loopback HTTP(S)。不再猜测 8787 端口,也不允许 remote override 携带凭据。
普通执行结束或取消即撤销。官方 shell 交给 jobs 的 background/promoted 执行绑定该 job,停止、结算、移除或 TTL 到期撤销;不跨任务复用。账号停用、owner 改变和 scope 不符均拒绝代理调用。长于 10 分钟的后台进程不能自动续期;应拆为新的受控调用。前台/后台都不会向 shell 注入 UAC 原始 token。需要 raw credentials 的受信 Host 调用必须显式申请 scope,且仍受 `allowRawAgentToken` 开关控制。
配套 `dsh-ops-cron` 已把 owner 校验和盖章前移到 agents.create/followup 之前,使用 uds-auth 时 owner 缺失/停用/冲突会拒绝执行。单独无 uds-auth 的 cron 使用方式保持可用。请同时部署本次 cron 变更。
## Gateway、持久化与权限
统一 Gateway 检查覆盖实际 `prepareInvocation` 路径:包括 unary、HTTP RPC 和 WS stream;每帧复核会话有效性、账号状态及 owner。现有资源接口继续调用各自 ACL;带 agent/workspaceFileScope/sessionId 的接口校验目标会话;其余全局接口默认要求管理员(包括账户、插件管理和未知新接口)。session/control baseline 和增量按可见会话投影。普通用户需要新增全局功能时,应先审查接口,再显式加入策略。
Gateway 内置 `$events` 同样校验登录和每帧有效性,按 owner 过滤会话事件、goal、Agent 审批和问答;普通用户只接收显式允许的公共目录变更,其他全局事件要求管理员。`$events/result` 必须来自创建该事件流的同一个本地登录 session,且仍能访问目标 Agent,避免跨用户代答审批。
该统一检查依赖当前 DSH `0.2.0-rc.2` 的 Gateway 内部方法;不兼容时拒绝加载,不能静默略过。升级 Host 前须重跑真实 Loader/Gateway 集成检查和完整 inventory。
roles 全部运行时写入串行化,未提交的权限不对读者生效。密码 generation 落盘并在重启时恢复;失败事务恢复角色、状态、偏好及口令,不让后台计时器写回失败状态。运行时 scrypt 使用异步线程池,最多 2 个并发、8 个等待,超载返回错误。
迁移使用排他复制、不覆盖冲突状态;相同的中断副本可继续,失败清理本次已创建且仍与源相同的文件,成功 marker 原子落盘。原子写入失败清理临时文件。
Unix profile 目录/文件为 0700/0600。Windows profile 及已知安全状态文件 DACL 仅允许当前 Host 账号和 SYSTEM,关闭继承,并实际读取 ACL 验证;失败拒绝初始化。服务账号部署须使用该账号执行初始化;换账号前由授权运维迁移所有权和 ACL。此机制不限制管理员提升权限后的访问。临时目录 DACL 测试不等于完整的第二个 Windows 普通账号读取测试。
## 交付检查
仓库提供 `npm test`(自然退出)、`npm run pack:check`、`npm run sbom`;CI 覆盖 Windows/Linux、Node 22.19/24、重建二维码、包检查及含开发依赖的 npm audit。`docs/sbom.cdx.json` 精确记录实际 bundled qrcode/dijkstrajs 和源文件哈希;`THIRD-PARTY-NOTICES.txt` 保留其许可证。DSH/React/Electron 属于 Host 的独立供应链,须结合正式 Host SBOM 检查。CI 配置落地不代表远端任务已经运行。

View file

@ -0,0 +1,18 @@
# 0.3.11:原版 Desktop 启动兼容修复
无需修改、重新打包或替换 DSH。修复全部在 uds-auth npm 包内。
0.3.10 的 Gateway 默认权限策略阻止了 Desktop 原生欢迎页在 UDS 登录前读取 `settings/describe`,宿主将业务拒绝当作启动失败,显示 `desktop welcome: Web RPC failed`。
0.3.11 对四个已有的、非流式 RPC 提供最小启动投影:
- `settings/describe`:仅语言偏好和虚拟启动命名空间,声明不可写且无本地文档。
- `llm/listConfigurableProviders`:仅虚拟启动条目,不枚举实际供应商配置。
- `credentials/describe`:仅接受固定虚拟引用 `UDS_AUTH_DESKTOP_PROVIDER_CONFIGURED`,返回“是否配置过供应商密钥”的合并布尔值,永远不可写。其他引用拒绝。
- `account/getState`:仅 Host 平台凭据是否存在的状态,登录尝试为 null,链接固定为公开平台页面。
这些投影不构造 UDS 身份,不赋予角色,不开放设置写入、凭据读写、会话或 WS 流。经过登录的管理员和可信 Host 调用仍获得原始权限对应的结果;普通用户只有投影,已撤销的登录身份仍拒绝。
如果 Host 还没有任何供应商凭据,原版 Desktop 可能仍显示自身的欢迎页;点击 Skip/跳过进入工作区后由 UDS 登录界面接管。原生欢迎页的账户登录和密钥写入仍须管理权限;请通过 UDS 管理员登录后的设置配置供应商。
安装 0.3.11 后重启 Desktop。请保持 0.3.10 的安全配置和角色数据,不要通过停用 uds-auth 或放行整个设置命名空间绕过此问题。