可配置的 DSH Web GUI 局域网访问:设置页开启后绑定全部网卡,只信任你配置的访问地址,可调整浏览器会话有效期或去掉 token 登录,并修复非回环访问时的设置层。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:longisland-icetea/dsh-lan-access
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
适用人群:本插件只适合已经配置好虚拟局域网(EasyTier / Tailscale / WireGuard / ZeroTier 等),或只打算从自己可控的安全局域网(家庭、办公室内网)访问 dsh Web 界面的用户。
⚠️ 安全提示:开启局域网访问意味着任何能访问该地址的人都能操作你的 Harness —— 相当于把远程代码执行暴露到该网段。请只在可信网络启用;不要在公共 Wi-Fi、校园网、共享机房网段启用;不要把启动日志里带 token 的 URL 转发给他人。
🤖 关于本仓库:本仓库的全部代码、文档与提交都由 AI 编码 Agent 生成,作者未逐行人工审阅。请自行审阅
cordis.patch.yml与lib/client.js(其中含对 dsh 内部结构的处理)后再使用。欢迎 issue / PR。
打开 DeepSeek Harness Web GUI 的局域网访问限制:让局域网内的其他设备(包括虚拟局域网 / VPN 网段)通过 局域网IP:端口 直接访问,并在设置界面提供一个选项卡配置要绑定的局域网 IP。
它解决什么
dsh web 有四层局域网护栏,本插件全部打开:
| 护栏 | 位置 | 本插件的处理 |
|---|---|---|
默认只监听 127.0.0.1 |
dsh-host-webserver(host schema 仅允许 127.0.0.1 / 0.0.0.0) |
启用后通过 patch 层把 webserver 行的 host 设为 0.0.0.0(全接口监听) |
CLI 拒绝 --host 0.0.0.0 |
dsh-web-app/startup |
走组合配置路径(patch 层),不经过 CLI 护栏;README 末尾有说明 |
/api 浏览器信任围栏 |
dsh-client-connection 的 trustedHosts |
围栏只信任你填写的地址;只有当你一个地址都没填时,才回退到 dsh 自动派生的信任项(见「严格围栏」) |
| 浏览器会话认证(token/ cookie) | dsh-client-connection |
启动时打印带 token 的局域网 URL;首次访问用 token URL 换取 cookie 后即可正常使用(这一层可以用 noAuth 整层关掉,见「免鉴权」) |
因为 webserver schema 只允许 127.0.0.1 / 0.0.0.0,插件不直接绑定某个具体 IP,而是:监听所有网卡(0.0.0.0)+ 信任你配置的地址。访问地址在设置页配置,效果与“绑定到这个局域网 IP”等价,且接口晚于启动出现(如 VPN 后启动)也不影响。
安装
dsh plugin --profile web add github:longisland-icetea/dsh-lan-access
# 后续升级:
dsh plugin --profile web update dsh-lan-access
⚠️ npm 上的
dsh-lan-access是别人的包(Leon0555,0.1.3,bind 0.0.0.0 +crypto.randomUUIDpolyfill),与本插件无关。本插件目前只从 GitHub 安装,不发布到 npm。
然后重启 dsh web(重启后设置页即可见“局域网访问”选项卡)。
使用
- 打开 Web UI 的 设置 → 局域网访问 选项卡。
- 勾选“打开局域网访问”,在“访问地址(局域的 IP)”里填你要用来访问的地址(可逗号分隔多个),也可以点“使用”自动填充检测到的本机 IPv4 地址。
- 点“保存”,重启
dsh web生效。 - 重启后终端会打印
dsh-lan-access: LAN: http://<ip>:<port>/?token=...。局域网设备用这个完整地址首次访问(换取浏览器 cookie),之后直接用干净地址即可。 - 不想做这一步、也不想被 cookie 到期/清缓存打断,就勾“免鉴权”:见下节。
严格围栏(0.2.0 起,破坏性变更)
dsh 自己在 webserver 绑定 0.0.0.0 时会信任所有非内部 IPv4(dsh-web-app 的 resolveLanTrust)。0.1.x 取的是并集,于是你在 accessHosts 里删掉的地址仍会被信任。0.2.0 起:
- 配置了地址 → 只有这些地址能过
/api围栏,dsh 自动信任的那份不再并入; - 一个地址都没配 → 回退到 dsh 的自动信任,否则「启用但没配」会把所有局域网访客 403 锁死。
想退回 0.1.x 的并集行为,把 cordis.patch.yml 里 connection 行 trustedHosts 表达式中「配置非空则直接返回配置」的分支改成「配置与 ctx.webRuntime.trustedHosts 取并集」即可。
注意:启动日志里
dsh web: ... (LAN: http://<ip>:<port>/?token=...)是 dsh 自己按lanAddresses[0]打印的,严格模式下未必可用;以插件自己那行dsh-lan-access: LAN: ...为准。
从局域网地址打开页面时的两个额外行为
dsh 有个客户端策略:persistence = ctx.remote.$host.isLoopback ? "host" : "memory"(dsh-client-ui-settings)。也就是说只要不是从 127.0.0.1 打开,整个设置层就退化成内存模式——读不到、写了也不落盘。本插件为远程访问补了两件事:
- 内测/欢迎声明不再每次刷新重弹:它的确认位存在
ui-onboarding.welcomeNoticeVersion,内存模式下永远读不到。插件用更低的priority注册一个同 id(welcome-notice)的空壳顶替该槽位(SlotCore 语义:同 id 低 priority 覆盖,"lowest renders"),渲染为空并立刻标记该步完成。只在非回环访问时注册。 - 设置层救援:把共享的 describe mirror 从 memory 拉回 host 并触发真实读取,让「提供方目录」「可配置插件列表」等不再报
settings are unavailable in this browser;已绑定的 namespace scope 因为在 bind 时就固定了persistence,插件会对SettingsScopeController原型上的getSnapshot/subscribe打惰性补丁,首次读取时补上 mirror 订阅并重算一次(derive()本身只读共享 mirror,与persistence无关)。
用 rescueSettings 关闭(默认开启):设置页「远程访问时修复设置层」勾选框,或直接写 ~/.dsh/settings.yaml:
lan-access:
rescueSettings: false
关闭后上述第 2 项完全不执行(第 1 项仍在,它只走公开槽位,不碰 dsh 内部结构)。
浏览器会话有效期(sessionDays)
用带 token 的地址访问一次后,cookie 会在那台设备上保存 sessionDays 天,期间直接打开干净地址即可,不必再翻启动日志。要点:
- 默认 30(等于 dsh 原值),可调范围 1–3650;设置页「浏览器会话有效期(天)」,或写进
settings.yaml的lan-access.sessionDays。改动需重启dsh web。 - cookie 按访问地址(host:port)分别保存,
.2与.5各一份,互不影响。 - 重启 dsh 不会让 cookie 失效 —— 校验只用到
~/.dsh/.credentials.yaml里持久化的签名密钥,不含进程随机数。真正的失效条件是:到期、删了.credentials.yaml、或浏览器清了 cookie。 - 调大的代价:拿到那台设备的人在这段时间内都能直接操作 Harness。
免鉴权(noAuth,0.4.0 起)
cookie 路径有个麻烦:换新设备、换浏览器、清了 cookie、或 cookie 到期,都得回头翻启动日志找那串 token。设置页「免鉴权(去掉启动 token 与 cookie 登录)」把这层整个摘掉——之后任何能连到这个端口的人直接就能用,不需要任何凭据:
lan-access:
enabled: true
accessHosts:
- 192.168.255.5
noAuth: true
生效方式(宿主机侧,见 lib/index.js 的 disableBrowserAuth):client-connection 只问两个问题,插件把这两个答案换掉——
| 原方法 | 原行为 | 免鉴权后 |
|---|---|---|
requestRejection(request) |
Host/Origin 不信任 → 403;没有有效 cookie → 401 |
只去掉 401;403(trustedHosts 围栏)原样保留 |
authorizeIndex(request, response) |
首屏要么拿 ?token= 换 cookie,要么出示 cookie |
一律放行;碰到陈旧的 ?token= 书签则 303 跳回干净地址 |
authenticatedUrl(baseUrl) |
拼上 ?token=… |
原样返回,启动日志那行 LAN: 也不再带 token |
要点:
- 默认关闭。开启后
sessionDays不再有意义(没有任何会话需要保活),设置页会把那一项置灰。 - 与「打开局域网访问」相互独立:
noAuth只管要不要凭据,不管监听地址。开着它、关掉局域网访问,本机127.0.0.1同样免登录。 - Host/Origin 围栏仍在:不在「访问地址」里的 Host 依旧
403。这层不是“登录”,是防 DNS rebinding / 跨站调用/api的,摘掉它等于把你浏览器里任何网页都变成 Harness 的客户端,所以本插件不提供这个开关。 - 其余全部交给网络:虚拟局域网 / VPN / 防火墙 / 网段隔离,谁都行——但请确认它们真的挡住了不该来的人。
- 改动需要重启
dsh web;生效时启动日志会打印dsh-lan-access: browser authentication removed (noAuth) ...。若打印的是... exposes no browser authentication to remove,说明你装的 dsh 改了connection的方法名,此时 dsh 的登录照旧(插件不会假装成功)。 - 它改的是
connection服务原型上的方法(dsh 内部结构)。补丁带还原函数,挂在插件 fiber 上随插件卸载一起撤销;和「设置层救援」一样,最坏情况是降级回 dsh 原行为,不会白屏。
工作原理(插件结构,0.6.0 起)
0.6.0 改为静态组合、零运行时行所有权:绑定与围栏由 bundle patch 里的 loader !!js 表达式在每次启动时求值一次决定,进程运行期间不再改任何行:
cordis.patch.yml(bundle 层)做三件事:insert:插件自身行lan-access(market 开关只会把disabled写在这一行上,永远碰不到官方行);- 按 id 覆盖
webserver行:host是表达式——lan-access条目启用且设置页「打开局域网访问」开启时为0.0.0.0,否则为官方表达式(ctx.webStartup.host ?? '127.0.0.1'); - 按 id 覆盖
connection行:trustedHosts在两个开关都开启时为「配置的accessHosts(严格围栏,配置即策略;为空才回退到ctx.webRuntime.trustedHosts)」,否则为官方表达式;cookieMaxAgeDays同理取sessionDays(默认 30)。
表达式通过
[...ctx.loader.entries()]读取lan-access条目的disabled状态(dshmarket 开关在启动前写进组合层的那一位),并通过注入的settings服务读settings.yaml的lan-access.enabled(设置页开关)——两者都在启动前就位,因此没有时序竞态;表达式全部失败闭合(任何求值错误都回退官方默认,绝不导致启动失败)。宿主端
lib/index.js:注册lan-access设置命名空间(enabled+accessHosts+rescueSettings+noAuth+sessionDays),暴露lanAccess/overviewRemote,启动后只读校验覆盖是否生效(不重写、不entry.update),按noAuth决定是否摘掉浏览器鉴权,并打印局域网 URL(免鉴权时不带 token)。浏览器端
lib/client.js:注册“局域网访问”设置选项卡(settings.section槽位),读写remote.settings,调用lanAccess/overview展示本机 IP 候选和生效状态。
开关语义 = 重启生效:设置页保存、或 market 里禁用/启用,都只改变组合层里的状态(market 写 patch 的 disabled、设置页写 settings 的 enabled),下次 dsh web 重启时表达式求值出新的绑定与围栏。运行中的进程保持原状,Remote 的 overview 会如实报告 live vs configured 差异(设置页文案本就写的是“重启后生效”)。
插件零运行时依赖(宿主端只 import Node 内置模块;设置 schema 为可调用对象,Remote 用鸭子类型绑定),因此无论以 registry、tarball 还是本地 link: 安装都能工作,也不与 dsh 安装里的模块副本发生实例冲突。
升级 dsh 后会不会失效?
设计目标是噪音式降级,而不是静默失效:
- 针对 dsh 官方行的覆盖是按 id 的 patch;某行 id 或结构变化时,组合器会警告并跳过该条(dsh 照常启动、完全回到官方行为),插件启动时的只读校验会打印
LAN access configured but not live ...,并提示“重启后若仍不生效,可能是该 dsh 版本改了webserver/connection行的结构”。 - 两条
!!js表达式失败闭合:任何求值错误都回退到官方默认(回环绑定 / 部署默认信任表),不会让 dsh 启动失败。 - 插件只依赖稳定的公开接缝:
settings服务、webStartup/webRuntime服务形状、loader.entries()的条目状态、connection的trustedHosts、settings.section/settings.onboarding槽位、remote.settings。 - 唯一碰 dsh 内部结构的两处是「设置层救援」对
SettingsScopeController原型、以及「免鉴权」对connection服务原型的补丁,都整段包在try/catch里(免鉴权找不到目标方法时只警告并跳过):dsh 若改了这些类,最坏结果是相应功能静默失效(救援失效则那几个卡片继续报settings are unavailable;免鉴权失效则继续要 token),不会白屏或启动失败。可分别用rescueSettings: false/noAuth: false关掉。
唯一无法防护的是 dsh 侧新增护栏(例如 webserver schema 以后拒绝 0.0.0.0,或 CLI 护栏搬到配置层)——那种情况下护栏会赢,插件会如实报告降级状态。
排查
| 现象 | 原因 / 处理 |
|---|---|
| 设置页选项卡不显示 | 插件行未挂载:检查 dsh --profile web --dump-config 里是否有 lan-access 行;确认 profile 的 dsh.profile.bundles 含 dsh-lan-access |
启动日志出现 LAN access configured but not live |
重启 dsh web 让表达式重新求值即可;若重启后依旧,说明该 dsh 版本改了 webserver / connection 行的 id 或结构(组合器已警告跳过),需要更新插件 |
| 局域网访问打不开(403) | 围栏未信任该地址:确认设置里填的地址规范(纯 IP 或 域名,host:port 均可),或该地址不在 webserver 正在监听的网卡上 |
| 局域网访问提示 401 | 需要带 token 的 URL 先换 cookie;重启 dsh web 看打印的 dsh-lan-access: LAN: 行。不想每次都来这一遍就开 noAuth |
开了 noAuth 还是 401 |
改动需重启 dsh web;重启后看日志里有没有 browser authentication removed (noAuth)。若出现 exposes no browser authentication to remove,说明该 dsh 版本的 connection 方法名变了,插件未接管(dsh 登录照旧) |
从局域网地址打开时报错 settings are unavailable in this browser |
设置层内存模式;确认 rescueSettings 未关闭,并且页面是从受信任地址打开的(严格围栏下未配置地址时会回退到 dsh 自动信任) |
| 从局域网地址打开时所有设置项都改不动、刷新后丢失 | 同上:这是 dsh 对非回环页面的既定行为,救援只补读取与写入通道;要完整体验请从 127.0.0.1 打开 |
安全说明
局域网访问等于把具备代码执行能力的界面暴露给该网段。只在可信网络启用,不要转发票面显示的带 token URL;token 每进程随机,cookie 按访问主机绑定。
开启 noAuth 后连这层凭据也没有了:任何能连到该端口的人都能直接操作 Harness。此时你的网络是唯一的门(虚拟局域网 / VPN / 防火墙 / 网段隔离),而 Host/Origin 围栏只是防止别人借你的浏览器跨站调用 /api,不是访问控制。
与 --host 0.0.0.0 护栏的关系
dsh web --host 0.0.0.0 在 CLI 层被明确拒绝(安全护栏)。本插件通过组合配置(bundle patch 层、启动时求值的 !!js 表达式)实现同样的“全接口监听”,这是插件的设计意图,也是它的全部功能边界——它不是一个通用“绕过 dsh 安全限制”的工具。
License
MIT
链接
同类插件
zhu1090093659/dsh-web-ui#packages/dsh-ssh★ 7488
SSH 远程运维面板:Web 终端、SFTP 传输、本地端口转发与一条命令并发集群执行,Agent 与面板共用同一份主机配置。
zhu1090093659/dsh-web#packages/dsh-remote-web-ui★ 7488
手机/PC 远程操控 dsh web 工作区:扫码配对、令牌门控通道、SSE 实时同步,提供移动端与完整桌面 GUI 两种远程形态。
ZSeven-W/dsh-ios★ 288
在对话里直接操作 iOS 模拟器或 USB 连接的 iPhone:22 个 Agent 工具用于启动、构建、按无障碍标识或 OCR 文本驱动 UI、列表行操作与 SwiftUI 预览热重载,并附带可点击拖拽的流式侧边栏面板。
saya-ch/dsh-mobile★ 260
通过 Android App 或手机浏览器访问 DeepSeek Harness,支持安全局域网连接、远程访问、持久设备配对和可自定义移动界面。
liguobao/deepseek-harness-remote★ 183
DeepSeek Harness 多端远程访问:从手机、平板、浏览器或另一台电脑继续进行中的会话,端到端加密通道(Noise IK + 自适应 Relay/WebRTC 传输),设备授权管理;远程端仅开放 ApiProxy 能力,支持 dsh-file-viewer 只读文件预览,不提供 Shell、远程桌面或写入权限。
wenbin-wb/dsh-bridge★ 158
DeepSeek Harness 远程与移动端接入插件:提供局域网扫码直连、Cloudflare 与自建公网隧道,以及微信、QQ、飞书、Telegram 机器人交互,内置安全认证与访问控制。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。