Skip to content

Releases: jasonchan2026/dsh-lan-chat

v2.2.2 — 端口被残留宿主占用后无法恢复

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 15:04

修复版本。让「端口被占用」真正能自助解决——之前重试按钮点了只会原地转圈。

修复

  • 端口被占用后无法恢复。 上一版的重试按钮本身没坏,它每次都在真的重新
    绑定端口;但占用者若是一个还活着的残留宿主进程,它永远不会自己让出端口,
    于是重试多少次都是同一个 EADDRINUSE,用户只能去重启 DSH 甚至手动找进程。

    典型场景:上一次 DSH 退出时宿主进程没被收干净,父进程变成 launchd(ppid=1),
    但它持有的监听 socket 仍然有效,继续占着 45871 和 45872。此时新开的 DSH
    里的插件就永远绑不上——而且它和残留进程读写的是同一份 identity.json,
    所以两个实例连 peerId 都一样,从界面完全看不出「其实有两个我在跑」。

    现在会把占用者查出来显示在提示里(PID + 已运行时长 + 判定原因),并在确认
    对方是残留宿主时提供「强制释放」按钮:SIGTERM 优雅退出,3 秒不退再
    SIGKILL,随后立即重新绑定,最多再重试 2 秒等内核回收 socket。

  • 误杀防护。 结束进程是危险操作,所以只在三条判据同时成立时才动手:

    1. 命令行里带 DSH 宿主入口(dsh-desktop-host / DeepSeek Harness);
    2. 父进程是 1(原来的 DSH 已经没了,它已被 launchd 收养);
    3. 不是当前进程本身,也不在当前进程的祖先链上(沿 ppid 上溯 32 层核对)。

    任何一条不满足就返回 409 并说明原因,绝不结束别人的程序。占用者是别的软件时,
    按钮根本不出现,提示仍然只建议改端口或手动关闭。

新增

  • GET /lan-chat/state 增加 holder 字段:{pid, command, orphan, reason, ppid, elapsed},
    查不到占用者时为 null。诊断结果按「端口 + 启动错误」缓存,不会每次轮询都起子进程。
  • POST /lan-chat/listen/release:强制释放端口并重新监听。已监听时幂等返回
    alreadyListening;占用者不可安全结束时返回 409。
  • scripts/hosttest.mjs 新增 14 项(97 → 111):占用者查找、自身保护、空端口、PID 复用复核、
    非法端口号、非残留进程时拒绝释放、GET 拒绝等。

升级说明

  • 协议 version 仍是 2,无需局域网内其他机器同步升级。
  • 建议升级后重启一次 DSH,让新版宿主接管端口。

v2.2.1 — 修复深色主题下配色失效

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 11:51

修复:深色主题下整块面板灰成一片

面板一直把 --dsw-alias-bg-l1 / --dsw-alias-bg-l2 当作背景色,但 DSH 主题里并没有这两个变量(真正存在的是 --dsw-alias-bg-base 和 --dsw-alias-bg-layer-1/2/3)。var() 解析不到就静默回退到写死的 #fff,而文字色走的是真实存在的 --dsw-alias-label-primary,深色主题下取值 #f9fafb。

结果就是白底配近白字,对比度只有 1.05:1。

这个缺陷从 1.0.0 就存在,只是浅色主题下兜底的 #fff 恰好等于正确值,一直没暴露。

改动

用途 原来(失效) 现在
面板 / 侧栏底色 --dsw-alias-bg-l1 ❌ --dsw-alias-bg-base
弹窗底板 --dsw-alias-bg-l2 ❌ --dsw-alias-bg-layer-2
气泡 / 进度条底色 --dsw-alias-bg-l2 ❌ --dsw-alias-bg-skeleton
强调块上的文字 写死 #fff --dsw-alias-label-primary-foreground
危险色 / 成功色 写死 #e5484d / #2ea043 --dsw-alias-state-error-primary / state-success-primary

顺带把 13 处写死的颜色(危险色半透明底、遮罩、弹窗阴影、成功提示等)改成由主题变量派生。深色主题下不再出现「亮红底配白字」这类发虚的组合。

新增测试

scripts/themetest.mjs(65 项),已接进 npm test:

  • 把插件引用的每个 --dsw-alias-* 和 DSH 主题比对,名字写错立刻失败。var() 引用不存在的变量不会报错,只会静默用兜底值——这类 bug 功能测试全绿也发现不了。
  • 用 DSH 真实的明 / 暗两套取值算 WCAG 对比度。
  • 断言明暗两套底色真的不同。

实测把修复前的代码喂给它,失败 16 项。

验证

在真实 DSH Web GUI 里按明 / 暗两种主题实测(macOS 当前是深色):

深色  面板底色 rgb(21,21,23)  文字 rgb(249,250,251)   对比度 17.45
浅色  面板底色 rgb(255,255,255) 文字 rgb(15,17,21)     对比度 18.90

升级说明

协议仍是 v2,与 2.2.0 完全兼容,不需要和局域网内其他机器同步升级。纯界面改动,不涉及数据格式,升级后不需要迁移。

测试:288 项全通过(selftest 34 / hosttest 97 / e2etest 40 / sectest 52 / themetest 65)。

v2.2.0 — 删除群聊(群主解散 / 成员删本机)

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 10:24

功能版本。补上「删除群聊」——之前群里只能退,界面上连退出入口都没有,建过的群永远留在会话列表里。

新增

删除群聊,按身份分两种语义:

  • 群主删除 = 解散:本机连群带消息一起删掉,并通知所有成员各自清掉。对方那边也看不到了。
  • 非群主删除 = 只删本机这份:相当于退群 + 清本地记录,不会解散别人的群。

入口两处:会话标题栏的关闭图标、会话列表行悬停时右侧出现的关闭图标。删除会同时清掉该群的本地消息与未读标记。

POST /lan-chat/groups/remove:请求体 {groupId},响应 {ok:true, dissolved:boolean}。群不存在返回 404。

协议层新增 group-removed 消息:群主解散时推给所有成员(在线走 WS,离线走 HTTP POST /lan-chat/v1/group-removed)。与 group 一样校验 createdBy,非群主发来的解散请求一律 403,防止冒充群主解散别人的群。

修复

  • WS 推送的群资料被静默丢弃:broadcastGroup 会 pushTo(member, {type:'group'}),但接收端 _onSocketText 里没有 group 分支,帧到达后直接丢掉。走 WS 的成员永远同步不到群资料,只有走 HTTP 的离线成员能收到。
  • 删掉的群会被推送复活:本机删群后,群主只要改一次群名,mergeGroup 就会把群重新建回来——「删除」形同虚设。现在删群记入 state.json 的 deletedGroups 黑名单(最多 500 条),重启后依然有效。

升级说明

  • 协议版本仍是 2。group-removed 是新增消息类型,不是破坏性变更:旧版本收到不认识的类型会忽略,不会报错或崩。建议两端都升到 2.2.0 才能双向同步解散。
  • state.json 会自动补上 deletedGroups: [],无需手工迁移。
  • 需重启 DSH 才能加载新代码。

测试

223 项通过:selftest 34 / hosttest 97 / e2etest 40 / sectest 52。新增 21 项,覆盖群主解散、非群主仅删本机、消息与未读一并清空、会话列表同步、重复删除 404、删除后不被推送复活、重启后黑名单保留、跨机解散同步到成员、以及新接口的鉴权与群主越权校验。

v2.1.1 — 桌面端写操作修复 / 端口占用自愈 / CSS 前缀隔离

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 09:49

修复版本。修掉桌面客户端上的两个阻断性问题,以及一处全局 CSS 命名冲突。

修复

1. 桌面客户端所有写操作报「已拒绝非同源请求」

DSH 桌面客户端不加载 http://127.0.0.1:<port>,而是走自定义协议 dsh-app://app,请求由 Electron 主进程的 forwardWebRequest() 转发 —— 它会把 host / origin / cookie / sec-fetch-site 四个请求头主动删掉再重发,而 Node 的 fetch 不会补 Origin。于是写请求到达插件时没有 Origin,撞上 2.0.0 引入的 F-09 规则(写操作必须带 Origin),全部 403:改昵称、发消息、上传文件、删消息、加联系人统统失败。浏览器网页端是真正的同源 fetch,一直是好的 —— 所以这个 bug 只在桌面端出现。

现在写操作额外接受 x-lan-chat-client: desktop 标识头。这不是放宽 CSRF 防护:跨站页面要带自定义头必须先过 CORS 预检,而本插件从不回 CORS 响应头、预检本身也会被同源检查拒掉,浏览器根本发不出去;跨站表单则无法自定义请求头。Host 白名单(挡 DNS rebinding)、Sec-Fetch-Site、跨站 Origin 三条检查一条没动。

2. 局域网端口被占用后永久失效(listen EADDRINUSE)

以前绑定失败只记一条 startError 就再也不试,必须重启 DSH 才能恢复。而端口占用常常是暂时的 —— 旧宿主进程残留、DSH 刚退出时 socket 还没释放、或用户重启过快。现在绑定失败后按 2s→60s 指数退避自动重试,恢复后通过 SSE 通知界面;提示条改成人话并带「重试」按钮可立即重试。

3. 全局 CSS 前缀与 dsh-context 冲突

全部 77 个类名、DOM id 和 11 个 CSS 变量从 lc- 改为 lanchat- 前缀,与 dsh-context 的 lc-* 彻底隔离。

升级说明

  • 协议版本仍是 2,与 2.0.0 / 2.1.0 完全互通,可以分批升级。
  • 无需迁移任何数据;如自定义过 CSS 覆盖 .lc-*,需改成 .lanchat-*。
  • 桌面客户端必须重启 DSH 才能加载本次修复。

测试

202 项通过:selftest 34 / hosttest 86 / e2etest 35 / sectest 47。hosttest 新增 13 项,覆盖桌面端标识头放行、标识头不能绕过跨站 Origin 与 DNS rebinding、端口占用时报错不崩溃、释放后重试恢复、重复重试幂等。

v2.1.0 — 联系人备注 / 可配文件上限 / 消息删除

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 09:00

新增

  • 联系人备注 —— 给同事起本地备注名,会话/联系人列表优先显示备注并附真实昵称。只存本机,不通知对方。
  • 单文件上限可配 —— 设置里 64MB ~ 64GB,默认 512MB。作用于浏览器上传和节点间文件回源两条路径,发送框提示实时跟随。
  • 删除消息 —— 单条消息悬停出现删除图标;会话标题栏可清空本会话。都只删本机日志,对方副本保留。

修复

  • 改名后对方看不到 —— /hello 响应里一直带着对方最新昵称,但接收方只取了 token 把昵称丢了。现在握手会回填昵称和端口。
  • 设置保存不再无条件广播昵称,只有昵称真的变了才推。

测试

170 → 189 项:

套件 之前 现在
selftest 32 34
hosttest 56 73
e2etest 35 35
sectest 47 47

升级说明

协议版本仍是 2,与 2.0.0 完全互通,可分批升级。state.json 会自动补上 maxFileBytes / maxTotalBytes,无需迁移。

v2.0.0 — 安全修复版

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 08:31

破坏性安全版本

修复 2026-10-25 渗透测试发现的全部 22 项安全问题。协议版本 1 → 2,
新旧版本不互通,局域网内所有机器必须一起升级。

严重(3 项)

  • 7 个接口完全无鉴权 — /send /sync /group /profile /upload /file /ws
    同网段任何人都能收发消息、读文件、改资料
  • WebSocket 无鉴权且可冒充 peerId — 能顶掉真人身份
  • 私聊消息被广播给所有已连接节点 — 私聊内容泄露给第三方

高危(9 项)

  • 消息发送者可任意伪造 → /send 用 token 持有者覆盖 from
  • 无鉴权任意文件写入 → 上传加鉴权 + 2 GiB 配额 + 7 天 GC
  • accessCode 只在 /hello 校验 → token 兜底
  • /state 明文返回访问码 → 改为 hasAccessCode
  • /lan-chat/* 绕过 DSH 鉴权围栏 → 21 条路由统一过同源检查
  • 缺少 Origin 时直接放行 → 写操作缺 Origin 一律 403
  • DNS rebinding → Host 回环白名单
  • SSRF → 出站/入站双向地址过滤,拒绝公网与云元数据
  • SSE 无鉴权持续推送 → 同源检查 + 并发/空闲上限

中低危(10 项)

绑定公网可达、任意网段扫描、群成员注入、WS 分片无上限、文件永不回收、
UDP 泄露身份、/diag 泄露路径、mime 未净化、死代码等,全部修复。

详见 CHANGELOG.md。

测试

109 → 170 项,全部通过:

套件 数量 内容
selftest 32 协议层
hosttest 56 宿主路由
e2etest 35 双机互联
sectest 47 安全回归(新增)

sectest.mjs 是把渗透测试用例改写成的环境无关回归测试,已接入 CI。

升级方式

dsh plugin --profile desktop add github:jasonchan2026/dsh-lan-chat

然后重启 DSH Desktop。

已知限制

不加密传输(局域网内可被同网段抓包),不要用它传敏感凭据。
未做 TLS 是刻意取舍——保持零依赖、免证书配置。

v1.0.0 — 首个公开版本

Choose a tag to compare

@jasonchan2026 jasonchan2026 released this 25 Oct 05:26

局域网聊天助手 v1.0.0

隔离内网的即时通讯插件,装完 DSH Desktop 左侧多一个「局域网聊天助手」入口。

功能

  • 单聊 / 群聊 — 消息按会话存 JSONL,支持增量拉取与已读标记
  • 文件传输 — 内容寻址存储(sha256),支持任意大小文件上传下载
  • 网段扫描加好友 — CIDR 并发探测(最多 1024 个地址),也可手动填 IP:端口
  • 自动发现 — UDP 广播每 30 秒发一次,同一局域网内的实例自动出现在「发现」页
  • 访问码鉴权(可选)— 留空 = 内网信任模型;设了码之后握手需要带对

安装

dsh plugin --profile desktop add github:jasonchan2026/dsh-lan-chat

装完重启 DSH Desktop,左侧出现「局域网聊天助手」。

也可以 clone 下来装本地目录:

git clone https://github.com/jasonchan2026/dsh-lan-chat
dsh plugin --profile desktop add link:$PWD/dsh-lan-chat

为什么不用 DSH 自带的 web server

DSH 的 webServer 只绑回环地址,局域网里其他机器连不上。所以插件自己监听 0.0.0.0:45871
(UDP 发现用 45872)用于机器间通信;浏览器侧仍然走 DSH 的同源路由。

零运行时依赖

全部用 Node 内置模块实现,包括手写的 RFC6455 WebSocket —— 所以在完全离线的机器上也能装,
只要能把包拷过去。

测试

176 项全部通过:协议层 28 + 宿主路由 47 + 界面 67 + 两台机器端到端 34。

安全提醒

默认 accessCode 为空,同一局域网内任何人都能连。这在隔离内网里是合理的内网信任模型,
但如果你的环境里有人不该看这些消息,请到插件设置里设一个访问码。