dsh-advanced-listening-settings
Verifieddsh-advanced-listening-settings · v1.0.1 · MIT · Web UI
DSH 高级监听设置(profile: web):全局/指定 IP 监听、设置端口、取消鉴权、远程访问兼容(宿主面判定 + 入口头规范化)、宿主重启,以及卸载前的一键完全清理。基于 dsh 0.1.5-rc.3,由 DSv4.1flash 构建。
Install
dsh plugin add dsh-advanced-listening-settings Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-advanced-listening-settings
给 DSH Web GUI 的「设置」加一个独立分区 高级监听设置:把 Web 服务暴露到局域网,按网卡 IP 精确控制暴露范围,并把远程访问下会失效的两处宿主限制补齐。
- 安装:
dsh plugin --profile web add dsh-advanced-listening-settings - 支持的 profile:
web - 兼容:dsh
0.1.5-rc.3;Node ≥ 22.6 - 卸载:默认态零残留,卸载前可一键完全清理
插件本体是一个标准的 DSH bundle 插件(dsh.bundle.patch + dsh.client):宿主半边 index.js、浏览器半边 client.js、bundle 补丁 cordis.patch.yml。
特性
| 控件 | 作用 | 生效方式 |
|---|---|---|
| 全局监听(0.0.0.0) | 绑定所有网卡 | 写入组合层 → 热重载重新绑定 |
| 本机 IP 列表 + 复选框 | 只监听勾选的网卡地址(默认全不选 = 仅本机) | 运行时立即生效 |
| 端口 | 对外服务端口,默认 3080;命令行 --port 优先(此时输入框置灰并显示实际端口) |
写入组合层 → 热重载重新绑定 |
| 取消鉴权 | 去掉 token(401)校验,直接访问 IP:端口 即可;Host/Origin 来源校验(403)保留 |
运行时立即生效 |
| 重启 dsh(二次确认) | 按原命令在后台拉起新实例 | 立即 |
| 访问地址…(二级页) | 列出全部可用入口 URL,一键复制;免鉴权时列不带 token 的地址 | — |
| 配置与清理…(二级页) | 查看托管块与文件位置、检测并清理历史遗留条目、移除托管块、完全清理(卸载前) | — |
界面预览

安装
# 从 npm
dsh plugin --profile web add dsh-advanced-listening-settings
# 或从本地目录 / tarball
dsh plugin --profile web add "C:\path\to\dsh-advanced-listening-settings"
dsh plugin --profile web add .\dsh-advanced-listening-settings-1.0.1.tgz
然后重启一次 dsh(面板里的「重启 dsh」按钮,或手动重启)。重启后:设置 → 高级监听设置。
本地目录安装建的是
link:依赖,目录必须留在原位;从 npm 安装则没有这个约束。
卸载与清理
插件刻意做到默认态零残留:没有配置过时不写任何文件;配置过的东西也能一键收回。
# 1) 先在面板「配置与清理… → 完全清理(卸载前)」点一下
# 它会删掉插件写下的一切:托管块、遗留条目、设置文件
# 2) 再卸载
dsh plugin --profile web remove dsh-advanced-listening-settings
# 3) 重启 dsh
忘了第 1 步也没关系——卸载之后插件虽然不在本地,清理命令依然能跑(从 npm 现场取一份):
npx -y dsh-advanced-listening-settings cleanup --dry-run # 先看会删什么
npx -y dsh-advanced-listening-settings cleanup # 真删
npx -y dsh-advanced-listening-settings cleanup --profile web --home "$env:USERPROFILE\.dsh"
pnpm remove 会自己清掉 profile 的 package.json 依赖、dsh.profile.bundles 条目、node_modules 链接与 pnpm-lock.yaml 条目;但它不会跑插件的代码,所以插件写在 profile 里的状态文件与组合层托管块,要靠上面这两条路收回。
启动失败时的急救:插件已对初始化异常做了兜底(失败只跳过自己,不影响宿主),仍可用 remove 摘掉它;若连启动都不行,直接手工删掉 cordis.patch.yml 里 >>> dsh-advanced-listening-settings 与 <<< dsh-advanced-listening-settings 之间那一段即可。
远程访问
把接口暴露到局域网只是第一步。DSH 的宿主与部分插件把「页面是不是回环来源」当作特权判据,远程页面天然不满足,于是出现两个只在远程访问时才犯的毛病:
| 现象 | 根因(已对源码定位) | 本插件的修复 |
|---|---|---|
| 设置 → 模型 显示 加载提供方目录失败: settings are unavailable in this browser | dsh-client-ui-settings 用 ctx.remote.$host.isLoopback ? 'host' : 'memory' 决定设置文档持久化;isLoopback 由 location.hostname 推导,局域网 IP 不是回环 → 降级 memory → 设置镜像永远为空 |
index 头部注入一次 globalThis.__DSH_TRANSPORT__ = { ownsHost: true }(仅在宿主未提供 transport 时),让远程页面按宿主面处理;客户端再对 connection.isLoopback 做一次可逆断言兜底 |
| 插件市场 → 系统诊断 里 npm / github / 目录站 不等超时、瞬间「不可达」 | dsh-plugin 的 isSameOrigin() 要求 Origin 主机名 ∈ {localhost, 127.0.0.1, [::1]},局域网 Origin 直接 403 untrusted origin,探测根本没跑 |
在 http.Server 的 request/upgrade 前置一层规范化:把 Host 是本机局域网 IP 的请求改写回 127.0.0.1:<port>(连带 Origin / Referer / Sec-Fetch-Site) |
安全边界:只改写 Host 恰好是本机某个非回环 IPv4 的请求。DNS rebinding 的攻击页面带的是攻击者域名,不会被改写,来源围栏照常 403;evil.example 这类未信任 Host 在测试中依旧被拒。
做法对齐社区成熟实现 dsh-pocket:它在反向代理里把 Host/Origin 统一改写成回环权威,并在客户端插件里断言
connection.isLoopback;本插件把同一件事直接挂在http.Server上,因此0.0.0.0与「指定 IP 直通」两种模式都覆盖,不需要额外代理进程。
自检接口:GET /advanced-listening/whoami 会回显宿主实际看到的权威与来源头,用来确认入口改写是否生效。
权限与安全
它会动什么
- 读当前 profile 目录(
~/.dsh/profiles/<profile>/):写设置文件、改cordis.patch.yml的托管块(改前备份.bak,写入走临时文件 + rename)。 - 覆写运行中
HostConnectionService的两个实例方法(requestRejection/authorizeIndex)以实现「取消鉴权」;停用 / 卸载时自动还原,且只还原属于自己的那一层。 - 在
webServer上注册/advanced-listening/*路由,并在 http 服务器前挂一层入口头规范化(只对本机局域网 IPv4 的 Host 生效)。 - 为勾选的网卡地址建立 TCP 直通监听(
net原样双向转发到127.0.0.1:<端口>)。 - 点「重启 dsh」时按原命令行在后台拉起新实例(等价于你自己重启一次 dsh)。
- 不写官方安装目录、不改官方源码、不采集任何数据。
外部服务:无。除你自己打开的设置页之外不发起任何外部请求;bin/cleanup.mjs 与测试套件也不联网(npx -y dsh-advanced-listening-settings cleanup 只从 npm 取一次包)。
安全须知
- 取消鉴权 + 局域网暴露 = 任何能访问该地址的人都能操控你的 DSH(执行命令、读写工作区文件)。面板上有红色警示。
- 免鉴权只去掉 token(401)校验,Host/Origin 围栏(403)仍在:
/与/api都会拦未信任 Host、跨站Origin、Sec-Fetch-Site: cross-site;入口改写只针对「Host 是本机局域网 IP」的请求,不对外部域名放行。 - 经过入口改写后,远程页面在宿主看来与回环页面同类(这也是「设置 / 市场诊断可用」的原因)。因此鉴权是远程访问唯一的门:请保留 token 鉴权,或只在完全可信的网络里关闭它。
- 只监听勾选的地址本身就是最小暴露面;不勾任何 IP 时等同默认(仅回环)。
- Windows 首次绑定非回环地址时系统防火墙可能弹窗;这是操作系统行为,不是插件行为。
兼容性:dsh 0.1.5-rc.3(已对源码核对);profile web;Node ≥ 22.6;Windows / macOS / Linux(重启器在 Windows 用 netstat + taskkill,POSIX 用 lsof + SIGTERM);仅 IPv4 网卡。DSH 处于开发者预览期,升级 dsh 后建议重跑 npm test 并确认面板仍能打开。
数据与文件
| 文件 | 什么时候存在 | 内容 |
|---|---|---|
~/.dsh/profiles/web/advanced-listening-settings.json |
只在偏离出厂默认时(非默认端口 / 全局监听 / 勾了 IP / 关了鉴权);回到默认自动删除 | 期望状态(模式 / IP 列表 / 端口 / 是否免鉴权),唯一真源 |
~/.dsh/profiles/web/cordis.patch.yml 的托管块 |
只在组合层默认值不够用时(0.0.0.0 或非默认端口);不需要时自动撤掉 |
webserver 的 host / port;写入前备份 .bak,写入走临时文件 + rename |
~/.dsh/logs/dsh-web-<端口>.log |
只有点过「重启 dsh」才有 | 重启后新实例的日志 |
插件不在 profile 之外写任何文件:重启器用 node -e 内联执行(两段式,源码经环境变量传给第二段),不落盘。
profile 目录优先从插件 entry 的 ctx.baseUrl 推断(即当前真正运行的 profile),扫描 profiles/*/package.json 只作兜底——避免多个 profile 都装了插件时写错文件。
实现说明
三条技术事实决定了实现形态(均已对 dsh 0.1.5-rc.3 源码核对):
- webserver 的绑定地址是硬编码的二选一(
Config.host只接受127.0.0.1/0.0.0.0),Node 也只支持一个监听地址。所以「只监听某几个网卡 IP」由本插件自己实现:webserver 保持127.0.0.1,插件为每个勾选地址建一条 TCP 直通监听(net原样双向转发到127.0.0.1:端口)。因为不解析 HTTP,普通请求、SSE、WebSocket(/api与 HMR)、gzip 全部透明可用;未勾选的地址连接直接被拒。 - host/port 是组合层(
cordis.patch.yml)的配置,只有 Loader 启动时读得到,所以绑定类改动必须落盘。web profile 模板的patchReload是live:写入后只会重启配置真正变化的行,webserver 重新绑定后各插件的路由会自动重挂。托管块保留命令行优先级:port: !!js ctx.webStartup.port ?? <保存值>,dsh web --port 9000依旧赢。组合层补丁是整段替换目标行的
config,所以托管块必须复述 webserver 行的全部 5 个键(host/port/compression/compressionLevel/compressionThresholdBytes)。test/patch-rewrite.test.mjs会守住这条契约。 - 鉴权是
HostConnectionService上的两个公开方法(requestRejection返回403/401,authorizeIndex负责 index 的 token/Cookie 交换)。插件在实例上做可逆覆写:只摘掉 401 那一层;authorizeIndex的覆写仍然先调用基函数,把 403 围栏原样保留(否则 DNS rebinding 的 Host 也能拿到 index.html)。插件停止或卸载时自动还原,且只还原「仍属于自己的那一层」。
开发与测试
# 离线单元测试(无需启动 dsh)
node test/patch-rewrite.test.mjs # 组合文件改写、默认态零残留判定、遗留条目识别、CRLF 行偏移
node test/ingress-normalize.test.mjs # 入口头改写:dsh-plugin isSameOrigin 与来源围栏的回归点
node test/client-bundle.test.mjs # 客户端 bundle:注册 id = 包名、isLoopback 断言与还原、settings.section 契约
node test/cleanup-cli.test.mjs # bin/cleanup.mjs:dry-run / 真清 / 幂等 / 不误删他人文件
node test/restart-helper.test.mjs # 内联重启器:argv 约定、零文件、两段式真实交接
node test/kill-port.mjs 3098 # 辅助:按端口清理遗留实例
npm test # 上面 5 套一起跑
npm run test:e2e # 隔离 profile 端到端(高位端口跑真实 dsh,绝不碰 3080 实例)
e2e-isolated.mjs 会在 test/.tmp/ 下造一个临时 DSH_HOME 与 web profile(base + web-app + 本插件),逐项验证:默认回环 + 鉴权、指定 IP 直通、全局监听、免鉴权与 403 围栏、局域网入口改写与市场 POST 判定、改端口热重载、重启后设置保持、完全清理后回到出厂态、不产生 profile 之外的文件。
restart-helper.test.mjs 与 e2e-isolated.mjs 需要调用 netstat / taskkill,请在普通终端(非受限沙箱)中运行。
已知边界
- 端口输入在
dsh web --port <n>启动时置灰:命令行优先级是组合层的语义,插件无法也不应绕过。 - 只支持 IPv4 网卡;IPv6 未纳入选择列表。
- 「指定 IP」依赖插件的 TCP 直通监听;插件被禁用 / 卸载时这些监听会立即关闭。
- 入口头改写只认「Host 是本机 IPv4 字面量」的请求:用主机名(如
my-pc.local:3080)或非本机地址访问时不会改写,来源围栏会按未信任 Host 处理(403)。远程访问请使用面板「访问地址…」里列出的 IP。 - 改代码(而不是改设置)不会触发热重载:升级插件后需要重启一次 dsh(面板「重启 dsh」或手动重启)才会加载新版本。
- 「完全清理」会把
cordis.patch.yml.bak一并删掉(那是本插件每次改 patch 前写的安全网);想留着加--keep-backup。 - 卸载回退到装前状态的前提是「先清理、后卸载」;
pnpm remove本身不会运行插件的代码,所以清理必须由面板按钮或bin/cleanup.mjs完成。 - 插件不修改官方安装目录,不 patch 官方源码,所有改动都在 profile 层与实例方法覆写上。
许可证
MIT © 2026 jcjyids