Skip to content

v0.1.7 — DSH 0.1.7-rc.2 适配、安全加固与全量缺陷修复

Choose a tag to compare

@Derpyu520 Derpyu520 released this 27 Sep 14:15
· 9 commits to main since this release

release: v0.1.7 — DSH 0.1.7-rc.2 适配、安全加固与全量缺陷修复

版本号与 DSH 对齐(0.1.7)。本次一次性包含自 0.1.5 以来的全部更新:
DSH 0.1.7 协议适配、控制台 UI 重构、token 消耗计费、安全修复与大批缺陷修复。

════════════════════════════════════════════════════════════════════
一、DSH 0.1.7 适配(本次的核心,三处接口断裂)
════════════════════════════════════════════════════════════════════

  1. 鉴权链路重建(最严重)
    DSH 0.1.7 的进程启动 token 改为「每进程现生成的 32 字节随机数,只存在内存
    WeakMap,不落盘、不可推导」,仅打印在 dsh web 的 URL 上。桥接原先靠读
    ~/.dsh/guard/logs/server-*.out.log 里的 ?token= 来发现它 —— 这条路彻底失效
    (读到的是上一个进程的陈旧 token,换 Cookie 只会得到 401,桥接静默失联)。
    新方案:用 ~/.dsh/.credentials.yaml 里持久化的
    client-connection/browser-session 签名密钥离线铸造会话 Cookie
    (Cookie 名 = dsh-auth-<base64url(sha256(authority))>,载荷含 authority/issuedAt/
    expiresAt,HMAC-SHA256 签名)。不再依赖任何进程期状态,DSH 重启/换启动方式都不需要
    重新适配。附带:到期前主动重铸、401 时在「铸造」与「token 交换」两种策略间自动回落。
    新增 scripts/test-dsh-auth.mjs(17 项断言)钉住该链路。

  2. agent preset 机制迁移
    0.1.7 的 preset 注册表既不扫描目录也不接受 preset 路径:preset 变成普通的
    Cordis 行(@deepseek-ai/dsh-agent-preset,config.plugins 必填),由 bundle patch
    插入。旧的 ~/.dsh/.agent-presets/ 目录机制已死,qq-chat / qq-chat-v2 会从
    清单里消失,而桥接是 strict 解析、找不到就 fail-closed ⇒ QQ 会话完全起不来。
    新增 plugins/qq-agent-presets/ bundle(含两个 preset patch + 权威守卫实现),
    由 scripts/build-agent-preset-patches.mjs 从仓库权威源生成,注册进 profile 的
    dependencies + bundles(重启后依然生效)。

  3. 工具改名与新危险工具
    守卫名单里的 cordis_define/cordis_undefine/cordis_run/cordis_stop 在 0.1.7
    已不存在(restrict 遇未知名字会静默失效 ⇒ 安全边界被无声破坏);同时 0.1.7 新增
    了 load_workspace_dependencies(自带 Python/Node/pnpm 绝对路径,等于执行能力)与
    list_subagent_models —— 旧守卫根本不认识它们。已全部按 0.1.7 实际注册的工具名对账。

  4. settings 写入契约(模式无法持久化)
    0.1.7 的 settings.write() 要求:loader entry 的 id 必须等于命名空间名、插件模块
    导出 Config、且 Config 至少有一个 meta.volatile 字段。qq-mode-console 三条全不
    满足 ⇒ 控制台改模式永远抛 No configurable plugin entry "qq-mode",模式只在本地生效、
    下一次 DSH 轮询即被覆盖回滚。已修正(entry id → qq-mode,新增 Config)。
    注意 .volatile() 只是 DSH 自带 schemastery 的语法糖,本仓库钉的版本没有该方法,
    故统一用两边都有的 .extra('volatile', true)。

════════════════════════════════════════════════════════════════════
二、安全修复
════════════════════════════════════════════════════════════════════

  • agent token 失效即提权(confused deputy):MCP 端在模型未传 token 时整个省略
    x-agent-token 头,而桥接把「无该头」等同于「管理端全权」⇒ 漏传令牌反而升级为控制台
    权限。现 MCP 端一律声明 x-agent-call: 1,桥接集中把关(不再依赖 40 余处端点各自判断),
    并修掉 /api/status 会把 ownerQQ/白名单/其它群 activity 回给模型的分支。
  • 明文凭据落盘:state/ 与 config.json 对 BUILTIN\Users 可读(含控制台令牌、
    SnowLuma OneBot 令牌 —— 后者等于 QQ 账号收发控制权)。现收紧为
    属主/SYSTEM/Administrators。
  • 控制台鉴权 fail-open 分支(if (!consoleToken) return true)→ 改为启动即拒绝。
  • 语音库绝对路径不再回给模型;提权命令改用环境变量传参(原为字符串拼接,路径含引号/
    反引号会在提权后执行)。
  • 新增 src/state-acl.mjs:ACL 逻辑收敛为唯一一份。原实现在 bridge.js 与
    scripts/harden-state-acl.mjs 各写一份且都带同一个 bug ——
    icacls DIR /inheritance:r /grant:r "u:(OI)(CI)F" /T 会把子文件 DACL 清空
    ((OI)(CI) 是目录专用标志,套到文件上失败,而 /inheritance:r 已摘掉继承 ACE),
    实测使 state/ 下 50 个文件变成空 DACL、连属主都读不了;叠加「回读自检被 Low 完整性
    标签行的 (I) 误判」⇒ 每次启动都重跑这个破坏性命令。现为安全四步流程 + 收紧后自检。

════════════════════════════════════════════════════════════════════
三、控制台 UI 与 token 计费(0.1.5 以来)
════════════════════════════════════════════════════════════════════

  • 控制台 UI 重构:设置页、逐轮花费视图、模式/角色面板;配套
    scripts/console-ui-fixture.mjs、test-console-ui.mjs 与 UI 契约清单。
  • token 消耗计费与账本:src/token-ledger.js + src/model-prices.js(含节假日分时价、
    跨年份价目表)、state/token-usage.jsonl 记账、控制台用量页与
    docs/guides/TOKEN_USAGE_CONSOLE.md。
  • 本次修掉的账本缺陷:reset() 未清 evicted/evictedDirty ⇒ 重置后仍显示旧花费,
    且下一次 compact 会把「已删除」的额度写回磁盘;series() 重复全量 buildIndex
    (3 秒轮询接口成本翻倍);turns 与 steps 恒等的死去重。

════════════════════════════════════════════════════════════════════
四、缺陷修复(本次审计发现)
════════════════════════════════════════════════════════════════════

  • 模式规范名 simulation 在所有输入口被静默拒绝(VALID_MODES 及其重复字面量),
    设定后 currentMode 会退回 chat(一代仿真:不同 preset、无 v2 工具闸门、不同发送
    路径),无日志无报错;同一处还修掉「在已处于 reserved2 时提交 simulation 会清空 v2
    定时器并排空待发队列」。现为单一别名表,VALID_MODES 由其派生。
  • 分条发送失败未记账 ⇒ 群里什么都没收到却被记为「已发言」并抑制后续接话。
  • 鉴权策略锁死:invalidateAuth() 被每次 WS 流结束调用,一次普通断线就永久改用过期
    token,此后所有 RPC 401。
  • 静默数据丢失:表情库与 sessions.json 读取失败被吞成「空库/空映射」,随后回写
    ⇒ 永久清零。现按 loadSlang 既有约定:不存在→空,损坏→抛错并降级只读不写。
  • /api/socialV2/config 在缺 socialV2 段时以 {} 为基线,导致工具开关归一化写成
    current.tools?.[k] !== false → true,一次控制台保存就静默打开默认关闭的
    setStickerRemark / sendVoice
    。
  • MCP 注册期工具过滤漏 10 个开关(含默认关闭的 setStickerRemark),默认关的工具
    仍随每次请求推送给模型并诱导其调用一个必然 403 的工具。
  • UTF-16 截断切断代理对(emoji 变乱码)共 15 处面向模型的位置,含一处 slice(-200)
    的镜像 bug;新增 truncateText/truncateTextTail,262,734 例属性测试 0 违例。
  • 长消息分条硬切代理对;黑话/研究结果提取的贪婪正则丢数据;config.json 的 BOM 被抹;
    角色缓存仅按 mtime 取键;KNOWN_AGENT_TOKENS 无界增长;浮动 Promise;sticker tmp
    残留;session-router 迁移分支静默无审计事件等。

════════════════════════════════════════════════════════════════════
五、隐私与发布卫生
════════════════════════════════════════════════════════════════════

  • 修复 .gitignore 的系统性失效:原排除规则按仓库根目录书写
    (/PROJECT_GUIDE.md、/RESEARCH_*.md、/TOKEN_COST.md …),但这些文档后来整体
    挪进 docs/,旧规则再也匹配不上 ⇒「原始开发文档不入公开仓库」的约定静默失效,
    6 份含本机真实路径的文件随时会被一并提交。已按新路径补回。
  • privacy-scan 现为绿(扫描 135 个入库文件),所有剩余命中均已逐条人工核对并记录
    判断依据(scripts/privacy-rules.json,本地文件、不入库)。
  • 修复自伤:.gitignore 注释里曾写入路径样式的示例字符串,本身即是一次命中。

════════════════════════════════════════════════════════════════════
六、测试
════════════════════════════════════════════════════════════════════

  • 审计套件 28/28;verify:adaptation 57/0;test:dsh-auth 17/0;
    src/core/session-router 33/33;privacy-scan 绿。
  • src/core/session-router.mjs 由 13/33 修至 33/33(该模块尚未接入生产,本次仅保证其
    自身与测试自洽)。
  • 说明:部分套件在受限沙箱内会因「无法 spawn 子进程 / 无法创建真实 symlink」而报假失败,
    需在普通终端运行;这属于环境限制,不是产品行为。