跳到正文
dsh-market 浏览插件 GitHub EN

idoall/dsh-lan-guard

把官方 DSH Web UI 安全地开放到局域网:不改动 DSH 自身的回环绑定,另开一个带密码门禁、自签 HTTPS 与可逐个吊销的设备配对的反向代理端口,并提供手机扫码入口。

Star 数 ★ 0 分类 远程与移动端 收录于 2026-09-25 npm dsh-lan-guard

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add dsh-lan-guard

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

截图

README

DSH LAN Guard 是 DeepSeek Harness 社区插件。它只注册官方设置页里的一个区块(settings.section),不替换任何官方布局,也不修改 DSH 源码或 DSH 自身的监听绑定。

DSH 的 Web 界面只监听 127.0.0.1,而官方明确拒绝绑定 0.0.0.0。本插件不碰那个绑定,而是在另一个端口上再开一个带门禁的监听,把官方界面原样代理出去:密码门禁、默认自签 HTTPS、手机扫码入口,以及可逐个批准与拉黑的设备配对。装完重启一次即可用——不需要手写任何配置。

能做什么

  • 门禁反向代理:完整转发 HTTP 与 WebSocket(含官方界面的 /api/remote.mux 长连接),改写 Host/Origin,剥离逐跳头,上游不可达时返回 502。DSH 自身的绑定与配置一动不动。
  • 默认对外可用,但对外可达 ≠ 可以进入:默认监听 0.0.0.0,所以安装后只需重启一次就能用;门禁默认开启,未设访问密码时拒绝所有设备,且默认自签 HTTPS——不存在明文传输。想只在本机使用,在设置页切到「仅本机」即可。
  • 双密码门禁:PBKDF2-SHA256(600,000 次迭代)。访问密码给手机等访客设备登录用,管理密码用于解锁本设置页的管理台(未设置时退回访问密码);另有 dsh_ 免密链接、持久访客会话、按 IP 锁定与 CSRF 校验。
  • 默认自签 HTTPS,CA 身份跨重启不变:自动生成 DSH LAN Guard CA,按所选网卡地址签发叶证书。换 IP 只重签叶证书,所以每台设备只需信任一次。
  • 本机永不锁定:直连 127.0.0.1 享有物理免锁特权(能用这台电脑的人本就能改这些设置);远程访问按 adminPolicy 处理——只读(默认)、需密码解锁、或不锁。
  • 设备配对与永久拉黑:手机首次通过门禁时自己命名一次,获得 HttpOnly 设备身份 cookie,出现在 已授权设备 里(名称 / 创建时间 / 最近使用 / 来源 IP),可逐个「吊销并拉黑」。拉黑不依赖设备指纹——换浏览器用密码重新配对也会被拒,「解除拉黑」是唯一的恢复方式。
  • 设置挂在官方设置页内:「局域网访问」分区含四个 tab——扫码访问、安全认证、已授权设备、连接与证书。排版与配色全部使用官方设计 token,不替换任何官方布局。
  • 远程也能添加工作区(智能分流):DSH 的目录选择器在启动时判定一次,本机回环绑定 + 有显示器会判成「原生」——手机点「添加工作区」实际是在电脑屏幕上弹文件夹对话框。本插件在浏览器侧以更低优先级遮蔽官方选择流程:本机浏览器照旧走系统原生对话框,远程设备改用页面内目录浏览器(面包屑、快捷入口、目录列表)。选中后仍由 DSH 官方工作区流程登记,插件只读目录、不写任何东西。
  • 端口与监听范围可配:默认 3081(DSH 端口 + 1),被占用时自动往后顺延(最多试 10 个);设置页可改端口并带可用性检查,也可在「局域网(默认)/ 仅本机」之间切换(两者都需重启 dsh 生效)。
  • 可选 mDNS:默认关闭;开启后广播 _dsh-lan-guard._tcp。
  • 升级检测:设置页显示「当前版本 → npm 上最新版本」并给出可复制的升级命令;插件不会自己安装或重启任何东西。

快速开始

环境要求:

  • 带 Web profile 的 DeepSeek Harness
  • Node.js 20 或更新
  • 已验证的 DeepSeek Harness:0.2.0-rc.2

从 npm 安装:

dsh plugin --profile web add dsh-lan-guard@latest

从 GitHub 安装:

dsh plugin --profile web add "github:idoall/dsh-lan-guard"

从本地克隆安装:

git clone https://github.com/idoall/dsh-lan-guard.git
cd dsh-lan-guard
pnpm install && pnpm run build
dsh plugin --profile web add "link:$(pwd)"

然后重启一次 DSH,打开 设置 → 局域网访问。重启后插件已经在对外监听(默认 0.0.0.0:3081,自签 HTTPS + 门禁),你只需要:

  1. 在 安全认证 里设置访问密码(至少 8 位)。没设之前,门禁拒绝所有设备。
  2. 在 连接与证书 确认监听范围(默认「局域网」),并选择对外公布的网卡——网卡决定二维码/访问地址用哪个 IP、自签证书签哪些地址。
  3. 在 扫码访问 扫码,手机上信任一次 DSH LAN Guard CA、输入访问密码、给设备命名。之后手机运行的就是官方 DSH 界面。

远程设备默认是只读的(adminPolicy: local_only):能用 DSH,但不能改插件设置。想让手机也能管理,在桌面把策略改掉。

设置

设置项集中在 设置 → 局域网访问 的四个 tab 里。非敏感开关(enabled、listenPort、listenHost、networkInterface、settingsUnlock、answerHeartbeat、socketWatchdog、mobileCompat、auth.mode、auth.adminPolicy、auth.adminProtection、auth.allowLoopback、auth.requirePairing、auth.requireApproval)可直接改;listenPort 与 listenHost 需重启 dsh 生效;settingsUnlock / socketWatchdog / mobileCompat / mobileScrollFix 刷新页面即可生效,answerHeartbeat 立即对已打开的连接生效;dataDir 与 tls.* 属启动期字段,需在 profile patch 里改。

设置项 默认 作用
监听范围 局域网 0.0.0.0 决定局域网能否访问这个端口。切到「仅本机 127.0.0.1」更保守,需重启 dsh。两种选择都保留门禁与自签 HTTPS。
代理端口 3081 DSH 端口 + 1;被占用时自动往后顺延(最多试 10 个),带可用性检查。需重启 dsh。
对外公布的网卡 自动选择 决定二维码/访问地址用哪个 IP、自签证书签哪些地址;虚拟网卡会被降权标注。
验证模式 扫码免密 + 密码 也可选「仅密码」或「仅安全 Token」。切换会吊销所有已有访客会话。
访问密码 未设置 手机等访客设备的登录密码。未设置时门禁拒绝所有设备。
管理密码 未设置 解锁本设置页的管理台;未设置时退回访问密码。
本机回环访问免密 开 127.0.0.1 直连跳过门禁(物理免锁)。
远程设备管理权限 仅本机 决定局域网设备能否管理:「仅本机」只读;「密码解锁」需先解锁;「不锁定」不额外要求。远程浏览目录并添加工作区需要后两者之一。
管理操作需要先解锁 开 关闭后,符合策略的远程会话可直接修改设置(local_only 除外,它永远只允许本机)。
局域网设备可用官方设置页 开 DSH 官方设置面默认只对回环页面开放,局域网设备打开「设置 → 模型」会提示 settings are unavailable in this browser。开启后,通过门禁的设备(含手机)刷新页面即可使用官方设置页;这是界面解锁,不是新增权限——设置接口本来就只由门禁把关,读取密钥仍由 DSH 脱敏。刷新页面生效,无需重启。
代理代答心跳 开 DSH 每 2 秒给每条 WebSocket 发心跳,连续两次没被回应就断开(实测 6 秒)。手机锁屏/切走时页面被系统挂起、回不了心跳,于是只走 WebSocket 的会话记录就会「载入不全、甚至断开」。开启后由代理替手机回心跳,手机自己回的那次是重复包,无副作用。立即生效(含已打开的连接)。
断线看门狗 开 页面补丁:卡在「连接中」超过 8 秒的 WebSocket 会被关掉;从后台回来 10 秒后仍一条都没连上时自动重载一次(每标签最多连续 3 次,冷却 20 秒起)。只对真的连不上的页面生效。刷新页面生效。
移动端兼容垫片 开 页面补丁:补 AbortSignal.any/AbortSignal.timeout/Promise.withResolvers/Iterator 与移动端 meta。缺这些 API 时 DSH 客户端在会话流里抛错,界面只会一直显示「载入历史…」且没有报错。现代浏览器上这些分支不生效。刷新页面生效。
手机滚动矫正(窄屏) 开 手机端 DSH 外壳在窄屏下把对话列裁在 overflow:hidden 的层里(实测 844/1688),整页也不可滚 → 内容可见但滑不动。开启后只在「窄屏 + 移动端 + 整页不可滚 + 找到被裁剪溢出的层」四条同时成立时,把那几层改成可触摸滚动;正常页面不碰。页面加 ?lgdiag=1 可看布局诊断。刷新页面生效。
新设备需要命名确认 开 新设备首次通过门禁时要自己命名一次,之后才出现在设备列表里。
新设备需要管理员批准 关 开启后,命名完还要你在设备列表点「批准」才能进入。
TLS 自签 HTTPS 关闭会明文传输门禁密码;非回环 + 关闭 TLS 必须显式设置 tls.allowInsecureLan: true,否则拒绝启动。

插件从它的 Cordis 条目读取配置。所有字段都有可用默认值,装完即可用:

# ~/.dsh/profiles/web/cordis.patch.yml(可选:只写你想改的字段)
- id: dsh-lan-guard
  config:
    listenHost: 0.0.0.0              # 默认面向局域网;'127.0.0.1' = 仅本机,或写具体网卡 IP
    listenPort: 3081                 # DSH 端口 + 1;被占用时自动顺延
    networkInterface: en0            # 可选:只在一个网卡上公布(留空 = 自动)
    settingsUnlock: true             # 局域网设备可用官方设置页(默认开;关闭恢复 DSH 默认)
    answerHeartbeat: true            # 代理代答 WebSocket 心跳(默认开;手机挂起时不被宿主回收)
    socketWatchdog: true             # 页面断线看门狗(默认开)
    mobileCompat: true               # 移动端兼容垫片(默认开)
    dataDir: ~/.dsh/profiles/web/data/dsh-lan-guard   # 可选;缺省即用这个推导路径
    tls:
      mode: self-signed              # 'self-signed'(默认)| 'provided' | 'off'
      allowInsecureLan: false        # 局域网明文 HTTP 的显式风险确认
    mdns:
      enabled: false                 # 广播 _dsh-lan-guard._tcp
    auth:
      mode: token_and_password       # 'token_and_password' | 'password' | 'token'
      adminPolicy: local_only        # 'local_only'(默认)| 'password_unlock' | 'open'
      adminProtection: true          # 管理台需要管理密码
      allowLoopback: true            # 127.0.0.1 访客跳过门禁(物理免锁)
      requirePairing: true           # 新的远程设备必须先命名一次

dataDir 是唯一需要解释的一项:不写就用 <当前 profile>/data/dsh-lan-guard(例如 ~/.dsh/profiles/web/data/dsh-lan-guard),写了就优先用你写的(支持 ~ 前缀)。它只影响插件私有数据(密码哈希、设备令牌哈希、会话、自签 CA)的落点,不影响可用性。

兼容性

当前版本:插件 0.4.6;兼容 DeepSeek Harness 0.2.0-rc.2(仅声明,零代码)——该版本修掉了 0.2.0-rc.1 的会话裁剪回归,移动端裁剪补偿因此不再介入(保留以兼容旧版本)。

插件 已验证的 DeepSeek Harness 这个版本是什么
0.4.6 0.2.0-rc.2、0.2.0-rc.1、0.1.7-rc.2 针对 0.2.0-rc.2 的验证版:代码零改动,只更新兼容元数据(dsh.compatibility.dshReleases 补 0.2.0-rc.2),并把两个宿主 devDependency 升到 0.2.0-rc.2 后重跑类型检查与全部用例。上游在 rc.1 → rc.2 之间只动了版本号(唯一源码改动是 ui-renderer 的 hooks 顺序修复,与本插件无关),因此本插件在 rc.2 上零改动可用;同时实测确认移动端裁剪补偿在 rc.2 上已无作用对象(被裁层 1 → 0,可滚范围 336px → 92343px,脚本每轮自检后整体撤销、不写任何内联样式)
0.4.5 0.2.0-rc.1、0.1.7-rc.2 长会话的正文是在官方滚动层拿到少量可滚范围之后才挂载的,旧逻辑把这个中间态当成「已正常」收工,晚出现的 overflow:hidden 层再也没人放行(真机实测:可滚范围 336px、滚动层内部仍有 1 层被裁)。现在改为在既有轮询窗口内继续侦测并放行迟到的裁剪层:可滚范围 336px → 6889px,被裁层 1 → 0,官方「回到底部」仍在屏内
0.4.4 0.2.0-rc.1、0.1.7-rc.2 修好 DSH 0.2.0-rc.1 上的 iOS 会话滚动:官方滚动层内部有一层 overflow:hidden 且高度锁死,把会话内容裁掉(真机实测:21083px 的内容只换来 336px 可滚范围),表现为打开会话不在最新、手指几乎拖不动。mobileScrollFix 现在让该层长高并脱离滚动容器身份,可滚范围恢复到 20675px;真机验收同时确认输入框仍吸底、官方「回到底部」按钮回到原位
0.4.3 0.2.0-rc.1、0.1.7-rc.2、0.1.7-rc.1 方向修正:不再给内容层打 overflow 补丁,改修移动端高度链——给 html/body/#root 确定高度、shell 用 100dvh、沿被裁层补 min-height:0,并自检「官方滚动层真的能滚」否则整体回退;移除自建「回到底部」按钮与全部 overflow-y/-webkit-overflow-scrolling 补丁;声明 DSH 0.2.0-rc.1 并把 dsh/peer 范围放宽到 <0.3.0
0.4.2 0.1.7-rc.2 找回官方「回到底部」按钮:窄屏矫正让 conversation 自己的滚动层高度不再增长,官方按钮因此永远认为还在尾部——插件在官方按钮缺席时补一个,官方按钮一恢复就自动让位;同时修复矫正的 overflow-y 补丁与全屏遮罩打架导致侧边栏被灰雾盖住的回归
0.4.1 0.1.7-rc.2 mobileScrollFix 首版,修「手机上聊天区滑不动」:仅在「窄屏 + 移动 UA + 整页不可滚 + 找到被裁剪溢出的层」四条同时成立时才处理,正常页面一律不碰;新增 ?lgdiag=1 布局诊断;真机实测连接存活 86.4 秒
0.4.0 0.1.7-rc.2、0.1.7-rc.1 手机长连接自愈:代理代答 DSH 的 WebSocket 心跳(实测把「停回心跳 6 秒被切断」变成「20 秒以上存活」);页面补丁加入断线看门狗与移动端兼容垫片;「连接与证书」新增连接体检与代理侧计数
0.3.6 0.1.7-rc.2、0.1.7-rc.1 远程也能添加工作区:浏览器侧遮蔽官方目录流程(本机仍走系统对话框,远程改用页面内目录浏览器,可在弹层内解锁);修掉代理双向丢弃插件管理 cookie 导致 password_unlock 对远程形同虚设;补上一直缺失的「远程设备管理权限」控件;「已保存」改为右上角提示;状态表面配色按实测重做;弹层两行被压扁重叠
0.3.5 0.1.7-rc.2、0.1.7-rc.1 连接层可见性:http:// 访问 TLS 端口由「空白页 + 无日志」改为 301 跳 https 并记日志;删除设备记录后旧 cookie 不再把浏览器锁死(吊销/拉黑仍拒绝);移除页文案同步修正
0.3.4 0.1.7-rc.2、0.1.7-rc.1 局域网设备可用官方设置页:新增默认开启的 settingsUnlock 开关(index 注入宿主界面标记,刷新页面生效;界面解锁而非新增权限)
0.3.3 0.1.7-rc.2、0.1.7-rc.1 门禁体验修复:首次访问的两道关提前说明;失效链接页恢复可登录;拉黑说明改为与实现一致(未触碰宿主接口)
0.3.2 0.1.7-rc.2、0.1.7-rc.1 装完即用:默认对外监听 + dataDir 自动推导;设置页液体玻璃与官方尺寸;访问地址改为单行滚动
0.3.1 0.1.7-rc.2、0.1.7-rc.1 针对 0.1.7-rc.2 的验证版:代码零改动,只更新兼容元数据
0.3.0 0.1.7-rc.1 设备批准与永久拉黑;修掉打开分享的 ?auth= 链接时的空白页
0.2.0 0.1.7-rc.1 升级检测;移除右下角状态胶囊;间距修复
0.1.1 0.1.7-rc.1 文档版:中英双语用户 README
0.1.0 0.1.7-rc.1 首个版本:门禁反向代理、自签 HTTPS、设备配对、设置页、扫码访问
  • 声明范围 >=0.1.7-rc.1 <0.3.0(dsh.engines.dsh);已验证版本:0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2。未列入的 DSH 版本属未验证,请自行验证后再使用。上界在 0.4.3 从 <0.2.0 放宽:profile 加载会拒绝范围不含运行版本的 bundle,而裸的 <0.2.0 恰好排除 0.2.0 正式版。
  • 本插件用到的宿主/客户端接口:webServer.register / webServer.tapIndex(indexTaps)、connection.requestRejection、connection.authenticatedUrl、追加型 settings.section seat、@deepseek-ai/schemastery,以及 profileContext(用于推导默认数据目录)。
  • 破坏性默认值变更(0.3.2 起):listenHost 默认由 127.0.0.1 改为 0.0.0.0,装完重启一次即可用;0.3.1 及更早默认仅回环。门禁与自签 TLS 的默认值未变(未设密码仍拒绝所有设备)。详见 CHANGELOG。
  • 0.4.0 的验证状态:三处改动都做了实测,不只跑用例。
    1. 代理代答心跳:先用 Node 客户端对真实 DSH 测得「停止回 Pong 后 6.0 秒被切断,close 1006(无 Close 帧)」;再加上代答后同一条链路 20 秒仍存活(收到 10 次 Ping)。仓库里有一条端到端用例复现同一对行为(上游按 40ms 心跳、漏 2 次即 destroy):关掉代答必被回收、打开代答连接存活。
    2. 页面补丁:注入的脚本在测试里被执行(假环境 + 假时钟),断言「卡在 CONNECTING 8 秒被关掉」「有一条 OPEN 就不重载」「从未建过 socket 不重载」「后台回来无 OPEN 才重载一次、并按冷却与上限退避」「隐藏状态不重载」。
    3. 浏览器实测(BrowserSkill,Chrome 154,经临时回环转发走完整插件链路):/api/remote.mux 握手 3ms、session/follow 首帧 2.1MB 单帧 43–110ms、12 秒 310 帧无中断;同一页面把主线程冻结 12 秒时,无代答的连接收到 close 1006。 测试套件 349 项全绿(0.3.6 为 330)。真实 DSH 上的装机验证在发布后进行(本机运行的是已安装的 0.3.6,需重装 + 重启 dsh 才生效)。

手机长连接为什么会断(实测,2026-09-28)

会话记录(就是界面上的「载入历史…」那一段)只走 WebSocket:会话列表、目标条、统计数字都来自 HTTP,所以 WS 一断就会呈现「界面都在、对话区空白」的形状。三个事实决定了这个失效模式:

事实 实测/来源
宿主每 2 秒 Ping 一次,连续两次没被回应才回收 websocketHeartbeatIntervalMs: 2000、MAX_MISSED_HEARTBEATS: 2
停止回 Pong 后 6.0 秒被切断(close 1006,无 Close 帧),三处独立复现 直连 3081、经转发、浏览器冻结主线程
手机切走/锁屏时页面被系统挂起,回不了心跳 iOS 行为;WebKit 另有「后台恢复后 new WebSocket() 永久卡 CONNECTING 而 HTTP 正常」的已知问题

因此本版本提供三层兜底,默认全开、可逐项关闭:

  1. 代理代答心跳(answerHeartbeat):代理在中转时读取上游→浏览器方向的 WS 控制帧,遇到 Ping 立刻以客户端身份回一个掩码 Pong。RFC 6455 允许未经请求的 Pong,宿主只按「收到过 Pong」重置计数,所以手机自己回的重复包无害。这是唯一能阻止宿主回收挂起手机连接的一层。
  2. 断线看门狗(socketWatchdog):页面侧兜住 WebKit 的「卡 CONNECTING」——超时关掉卡住的 socket,从后台回来仍连不上时限流重载一次。
  3. 移动端兼容垫片(mobileCompat):补 AbortSignal.any/timeout、Promise.withResolvers、Iterator 与移动端 meta。DSH 客户端在会话流里直接调用 AbortSignal.any,而 Session.doOpen 会把非传输类异常原样抛出——缺 API 时的表现就是「永久载入历史…、连报错都没有」。

「连接与证书 → 手机连接与自愈」里有一个连接体检:它在这个浏览器里检查缺哪些 API、页面拿到了哪些补丁,并对当前地址做一次真实 WebSocket 握手,同时显示代理侧的计数(在活连接 / 累计升级 / 被拒 / 代答心跳次数)与最近一条连接的时长、上下行字节、是否异常断开。

  • 0.3.6 的验证状态:改动包含宿主侧(新增目录列举路由与 cookie 中转)与客户端(目录浏览器、设置页控件、提示层与配色)。测试套件 330 项全绿(+57);三个提交在独立 worktree 里逐提交验证(278 / 304 / 330 各自通过);并做过反证(回退 cookie 中转、固定行不收缩、hover 染当背景,各自让对应用例失败);配色对比度按 DSH 真实 token 逐项计算,浅色/深色 10/10 通过。真实 DSH 上的装机验证在发布后进行。
  • 0.3.5 的验证状态:改动只在代理的连接层处理与门禁判定,未改动任何宿主/客户端接口;测试套件 273 项全绿(+10),并做过反证(回退修复后对应用例失败);真实 DSH 上的装机验证在发布后进行。
  • 0.3.4 的验证状态:改动只新增一个宿主侧开关、一行 index 注入与插件自己的设置页开关(webServer.tapIndex 是已声明的宿主接口);测试套件 263 项全绿;注入脚本已在真实 Chrome + 非回环地址实测三种状态;真实 DSH 上的装机验证在发布后进行。
  • 0.3.3 的验证状态:改动只在插件自己的页面、文案与门禁表单可用性,未改动任何宿主/客户端接口;测试套件 247 项全绿;真实 DSH 上的装机验证在发布后进行。

官方 UI 零改动复用,手机视口下自动适配:

手机上为什么「看不到全部消息」/ 滑不动(实测,2026-09-28)

症状:手机上打开会话,最新内容能看到一部分,但手指滑不动;底部固定的「目标条 / 快捷回复 / 输入框」压住正文,右侧滚动条停在中途。

根因(真机视口实测):DSH 官方外壳在窄屏下的根层 pI_x6G_frame 是 overflow:hidden,而内容高于它——实测 clientHeight=844 / scrollHeight=1688,同时整页也不可滚(pageScrolls=false)。内容被裁在容器里,所以"看得见、滑不动"。同一视口在桌面 Chrome 上正常,因此这是 iOS/窄屏的布局差异,与反向代理无关(DOM/CSS 全部来自官方 UI 与已装插件)。

修复(mobileScrollFix,默认开):注入脚本只在四条同时成立时才动手——窄屏(≤1023px)、移动端 UA、整页不可滚、且确实找到被裁剪且内容溢出的层;然后只把那几层改成可触摸滚动(overflow-y:auto、-webkit-overflow-scrolling:touch、touch-action:pan-y)。能正常滚动的页面一律不碰。

「回到底部」按钮(0.4.2 起):窄屏下官方按钮的显示条件是「不在尾部」,而它判断的是官方认定的滚动层,与实际滚动层不一致,于是永远不出现;网关侧因此自带一个等价的浮动按钮(不在尾部时显示、点击平滑到底、官方按钮出现即让位、位置按输入框动态计算)。

自检:在页面地址后加 ?lgdiag=1,顶部会出现一屏诊断(narrow / mobile / innerHeight / visualViewport / pageScrolls / clippingLayers / 是否已修)。这一屏就是本次定位所用的数据,以后复现同类问题不必连 Mac 调试。

安全边界

  • DSH 自身的监听地址不被改动;本插件从不修改 DSH 配置、会话数据或官方 UI。
  • 门禁先于监听:监听器只在门禁对象构造完成之后才打开。默认对外监听之所以可接受,是因为 auth.enabled 默认为真、未设访问密码时门禁拒绝所有非回环设备、且 TLS 默认自签——三者必须同时成立。
  • 密钥(secrets.json、devices.json、会话)存放在 dataDir,权限 600;密码只存 PBKDF2-SHA256 哈希,设备令牌明文只返回一次,落盘只存 SHA-256 哈希,免密链接的 token 不写日志。
  • 代理给每个转发请求打上不可伪造的来源标记,宿主据此区分「本机操作者」与「经代理的访客」。
  • 回环直连按设计物理免锁——能使用这台电脑的人本就能改这些设置。
  • 官方设置页解锁(settingsUnlock)是界面层的兼容补丁,不是新的传输权限:经代理的请求本就会被改写成回环 host,DSH 的设置接口只由门禁把关(读取被 DSH 脱敏),该开关只是让官方页面不再显示「settings are unavailable in this browser」。想恢复 DSH 原生行为,把开关关掉即可。
  • 访问密码是共享的:吊销设备会立即让该设备的身份 cookie 失效,但换个浏览器用密码仍可重新配对。要「同一台机器永久拉黑」需要设备指纹或每设备独立令牌,本项目刻意不用指纹。
  • Cookie 中转只有一个例外:代理注入 DSH 的回环会话 cookie,且绝不把上游的 set-cookie 透传给访客;唯一被中转的是本插件自己的管理会话 cookie(dsh_lan_guard_admin,HttpOnly、SameSite=Strict、默认 30 分钟)。它由本插件签发、也只由本插件的路由校验,但校验代码跑在 DSH 的 web server 上——不中转它,远程设备就永远无法解锁管理台(password_unlock 形同虚设)。访客的门禁会话与设备身份 cookie 依旧不会到达 DSH。
  • 远程目录浏览器只读,且要管理台同等的权限:它只列举目录名(凭据目录 .ssh/.aws/.env 等与系统目录一律拒绝,符号链接解析到真实路径后二次校验,单层上限 1000 行、带限流),不创建、不修改任何文件;登记工作区仍由 DSH 官方流程完成。该接口复用管理台鉴权——本机操作者或已解锁的远程会话可用,默认 local_only 下远程设备得到 read_only_remote。
  • 只面向局域网:不做公网隧道、不做 IM Bot、不做端口转发。
  • 插件自己不安装、不重启、不推送任何东西:升级命令由你复制执行。

排障

手机提示证书不受信任。 CA 是自签的:每台设备安装/信任一次 DSH LAN Guard CA。信任前先比对 连接与证书 里显示的 SHA-256 指纹。

手机完全连不上。 确认手机在同一网络、地址与二维码一致,并检查是否有 VPN 或「专用代理/中继」类功能拦截流量;也确认 监听范围 没有被切成「仅本机」。

设置页显示「端口已对局域网开放,但尚未设置访问密码」。 这是预期的中间状态:端口可达,但门禁拒绝所有设备,不会泄露数据。去 安全认证 设一个访问密码即可。

「配置的端口 X 已被占用,已自动改用 Y」。 端口被别的程序占着,插件已自行顺延。可在 连接与证书 换端口(带可用性检查),或释放该端口。

局域网设备打开「设置 → 模型」提示 加载提供商目录失败: settings are unavailable in this browser。 这不是本插件的故障:DSH 按页面地址栏是否为回环决定官方设置面是否可用,局域网地址不是回环,于是设置面被永久降级。打开 连接与证书 → 局域网设备可用官方设置页(默认已开),然后在那个设备上刷新页面即可。若仍不行,确认开关确实保存成功、页面是刷新而非切换 tab,并确认 DSH 版本仍在兼容范围内。

「此设备已被移除访问权限」(403)。 该设备已在 已授权设备 中被吊销或拉黑。删除那条记录即可让它重新配对(被拉黑的需先「解除拉黑」)。

远程点「添加工作区」没反应(或对话框开在电脑上)。 这是 DSH 目录选择器的机制:它在启动时判定一次,本机回环绑定 + 有显示器 ⇒ 判成「原生」,对话框开在电脑屏幕上,远程设备看不到也点不到。本插件已把它接管为「本机走系统原生对话框 / 远程走页面内目录浏览器」两套交互。

若远程弹出「远程设备当前只读」,说明策略还是「仅本机」:去电脑的 设置 → 局域网访问 → 安全认证 → 远程设备管理权限 选「密码解锁」(或「不锁定」),然后在远程设备上刷新页面。

若弹出「需要先解锁管理控制台」,直接在弹层里输入管理密码(未设管理密码时用访问密码)点「解锁」,列表会立刻出现——不必再去找设置页的锁定卡片。

我忘了访问密码。 在运行 DSH 的电脑上直连 http://127.0.0.1:3080(本机直连物理免锁)重设。无头服务器则删除 dataDir 下的 secrets.json 后重设——在此之前门禁会拒绝所有设备。

改过密码后所有设备都要重新输密码。 这是有意的:更换访问密码或切换验证模式会吊销所有已有访客会话。

局域网明文 HTTP 被拒绝。 非回环 listenHost + tls.mode: 'off' 会被拒绝,除非显式设置 tls.allowInsecureLan: true——否则门禁密码将明文传输。

升级

设置页会显示「当前版本 → npm 上最新版本」,并给出可复制的升级命令:

dsh plugin --profile web add dsh-lan-guard@latest

插件不会自己安装任何东西,也不会重启 DSH——命令由你执行,执行后手动重启一次 dsh。检测只访问公开的 npm registry,结果缓存 6 小时;连不上时只在界面上提示,不影响门禁与代理。

卸载

dsh plugin --profile web remove dsh-lan-guard
rm -rf ~/.dsh/profiles/web/data/dsh-lan-guard   # 可选:删除密钥、设备记录与 CA

开发

pnpm install
pnpm test          # 单元 + 集成测试(含类型检查)
pnpm run build     # 打包 lib/index.js 与 lib/client.js
pnpm run verify    # 类型检查 + 测试 + 构建 + pack dry-run

客户端半边注册到官方追加型 seat settings.section,宿主半边通过包内 cordis.patch.yml 挂载。

文档索引

文档 用途
docs/dsh-version-adaptation.md DSH 升级后照着走:diff 哪些包、核对哪些接口、怎么落声明与发版
docs/mobile-regression.md 电脑上就能跑的窄屏几何回归:两个入口、四个量化判据、免 token 直连 3080 的方法
docs/mobile-acceptance.md 真机验收清单(触摸、锁屏/切后台、语音这些本地验不了的)
docs/mobile-debug-runbook.md 手机端出问题时的排障手册(含 Web Inspector 探针)
docs/upstream-dsh-0.2.0-rc.1-session-scroll.md 投递上游的回归报告底稿(rc.1 会话裁剪)

发版

发版由 tag 驱动。更新 package.json、把对应 CHANGELOG 段落移出 Unreleased、编写带中英双锚点的 release-notes/v<版本>.md 后,推送发版提交与 tag:

git tag v0.3.1
git push origin v0.3.1

发版工作流会校验 tag 与 package.json 版本一致、要求发布说明含中英双锚点,然后运行 pnpm run verify、打包插件、通过 npm trusted publishing(OIDC)发布,并创建附带 tarball 的 GitHub Release。

许可

MIT

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。