Repository navigation
Releases: jasonchan2026/dsh-lan-chat
Release list
v2.2.2 — 端口被残留宿主占用后无法恢复
修复版本。让「端口被占用」真正能自助解决——之前重试按钮点了只会原地转圈。
修复
-
端口被占用后无法恢复。 上一版的重试按钮本身没坏,它每次都在真的重新
绑定端口;但占用者若是一个还活着的残留宿主进程,它永远不会自己让出端口,
于是重试多少次都是同一个EADDRINUSE,用户只能去重启 DSH 甚至手动找进程。典型场景:上一次 DSH 退出时宿主进程没被收干净,父进程变成
launchd(ppid=1),
但它持有的监听 socket 仍然有效,继续占着45871和45872。此时新开的 DSH
里的插件就永远绑不上——而且它和残留进程读写的是同一份identity.json,
所以两个实例连peerId都一样,从界面完全看不出「其实有两个我在跑」。现在会把占用者查出来显示在提示里(PID + 已运行时长 + 判定原因),并在确认
对方是残留宿主时提供「强制释放」按钮:SIGTERM优雅退出,3 秒不退再
SIGKILL,随后立即重新绑定,最多再重试 2 秒等内核回收 socket。 -
误杀防护。 结束进程是危险操作,所以只在三条判据同时成立时才动手:
- 命令行里带 DSH 宿主入口(
dsh-desktop-host/DeepSeek Harness); - 父进程是
1(原来的 DSH 已经没了,它已被 launchd 收养); - 不是当前进程本身,也不在当前进程的祖先链上(沿
ppid上溯 32 层核对)。
任何一条不满足就返回
409并说明原因,绝不结束别人的程序。占用者是别的软件时,
按钮根本不出现,提示仍然只建议改端口或手动关闭。 - 命令行里带 DSH 宿主入口(
新增
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 — 修复深色主题下配色失效
修复:深色主题下整块面板灰成一片
面板一直把 --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 — 删除群聊(群主解散 / 成员删本机)
功能版本。补上「删除群聊」——之前群里只能退,界面上连退出入口都没有,建过的群永远留在会话列表里。
新增
删除群聊,按身份分两种语义:
- 群主删除 = 解散:本机连群带消息一起删掉,并通知所有成员各自清掉。对方那边也看不到了。
- 非群主删除 = 只删本机这份:相当于退群 + 清本地记录,不会解散别人的群。
入口两处:会话标题栏的关闭图标、会话列表行悬停时右侧出现的关闭图标。删除会同时清掉该群的本地消息与未读标记。
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 前缀隔离
修复版本。修掉桌面客户端上的两个阻断性问题,以及一处全局 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 — 联系人备注 / 可配文件上限 / 消息删除
新增
- 联系人备注 —— 给同事起本地备注名,会话/联系人列表优先显示备注并附真实昵称。只存本机,不通知对方。
- 单文件上限可配 —— 设置里 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 — 安全修复版
破坏性安全版本
修复 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 — 首个公开版本
局域网聊天助手 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 为空,同一局域网内任何人都能连。这在隔离内网里是合理的内网信任模型,
但如果你的环境里有人不该看这些消息,请到插件设置里设一个访问码。