dsh-web-guard:给 DSH Web UI 加一道密码闸门,并修掉反代后的 /api 403

前言

DSH 的 Web UI 长期只有进程级 ?token=:每次重启换一次,得从终端抄出来,HTTP 反代之后 /api 还容易直接 403。dsh-web-guard(仓库 lvxianchao/dsh-web-guard,维护者 lvxianchao,MIT 许可证)把这几个部署问题收进一个插件:一个访问密码、可吊销会话、原生设置页,以及 TLS。下面介绍它的定位、安装、配置和运维注意事项。

生态背景:DSH 的理念是「一切皆插件」;本文的目录页来自第三方社区站点 SkillHub,与 DeepSeek 官方无从属关系。星标 1、分类 admin-security 为该目录页所载,截至撰写时无其他独立佐证。

它解决什么

README 把原生行为与插件行为对比成一张表,四个问题如下:

问题 原生行为 dsh-web-guard
没有人类登录 只有启动时打印的 ?token=,重启就换 一个密码 + 可吊销会话
反代一律 403 /api 栅栏要求 Host 原样可达,NPM/Caddy 会改写它 代理把 Host/Origin 改写成回环 authority
token 要挖日志 得 journalctl \| grep token 才能进站 进程内兑换,浏览器见不到 token
明文 HTTP 报错 crypto.randomUUID is not a function 注入 polyfill

零运行时依赖,只用 Node 标准库;npm 包里是预构建好的 JavaScript,dsh plugin add 不需要工具链。

它怎么工作

browser ──HTTP(S)──> dsh-web-guard :3081 ──> DSH listener (loopback)
                          │
                          ├─ not signed in → /login
                          ├─ signed in → forward, Host/Origin rewritten to loopback
                          └─ injects the harness cookie (never exposed to the browser)

DSH 的 /api 栅栏接受回环 authority,所以让每个上游请求看起来都来自回环,就消掉了 403。鉴权发生在代理里,不是前端遮罩:没有会话 cookie 的请求到不了 DSH。浏览器半(dsh.client)只负责把管理页挂进 DSH 的设置面板;闸门本身完全在服务端,浏览器半加载失败也不影响使用。

安装与启用

三种安装方式,命令来自 README:

# npm
dsh plugin --profile web add @lvxianchao/dsh-web-guard

# from GitHub
dsh plugin --profile web add "github:lvxianchao/dsh-web-guard"

# from a local checkout
dsh plugin --profile web add /path/to/dsh-web-guard

main 分支只放已发布代码(开发在 dev 分支),所以这条命令装到的是最新稳定版。包里声明了 dsh.bundle,guard 行会自动叠加,装完重启 dsh 即可。

首次启动会自动生成一个密码,打印在终端,并以哈希形式存到 <profile>/data/dsh-web-guard.json(0600)。想固定实例,改 ~/.dsh/profiles/web/cordis.patch.yml:

- id: guard
  config:
    listenHost: '0.0.0.0'
    listenPort: 3081

注意 profile patch 会整体替换 config,要保留的键都得重写一遍。

从 v2 升级:右下角浮动盾牌和 /__guard/admin 已移除,管理入口改为「设置 → DSH Web Guard」。重新安装(或升级到 3.0.0)并重启 DSH;密码哈希、会话缓存和设置文件格式不变,升级不踢人。

前提:DSH 自己必须绑回环(默认即 127.0.0.1)。如果绑在局域网 IP 上,客户端可以绕过闸门直连——插件启动时会检测并告警。

典型用法

让插件终结 TLS(没有反向代理)

config:
  listenHost: '0.0.0.0'
  listenPort: 8443
  tls:
    certFile: /path/fullchain.pem
    keyFile: /path/privkey.pem

浏览器访问 https://<host>:8443,会话 cookie 自动带 Secure。

放在反代之后

反代只做 TLS,上游填 http://<dsh-host>:3081,并开启 WebSocket。不需要任何 Host 改写配置——这正是插件要消掉的部分。

纯局域网

明文 HTTP + 密码,最简单也最不安全,会话 cookie 可被嗅探。

常用配置键

键 默认 说明
listenHost 0.0.0.0 闸门监听地址
listenPort 3081 闸门端口,不能与 DSH 端口相同
password — 明文密码(启动时哈希)
passwordHash — scrypt$N$r$p$salt$key,优先于 password
sessionDays 7 会话时长;0 = 关浏览器即失效
maxAttempts 5 连续失败次数,超过临时锁定
lockMinutes 5 锁定时长
polyfill true 注入 crypto.randomUUID 兜底
title DSH Web Guard 登录页标题
tls null {certFile, keyFile},启用 HTTPS

生成 scrypt 哈希:

node -e "import('./lib/auth.js').then(m=>console.log(m.hashPassword(process.argv[1])))" 'your-password'

它是什么,不是什么

这是一道「入口」,不是可选插件。装上之后,它监听 3081,请求转给只听回环的 DSH(3080)。插件一旦不加载,3081 就停止监听,内网和外网会同时打不开——而 DSH 本体还在跑,会话和数据都在,只是够不着。如果已经把这地址开放到公网,这一点需要格外当心。

安全模型上要说清楚边界:一个密码、一个权限级别,它不是多用户系统。DSH 的 agent 本身能执行代码,任何登录者都拥有该机器的执行权限——这是产品设计,不是闸门的缺陷。

前面表格里做的事——把 /api 的 403 归因于 Host 被反代改写,以及「代理把 Host 改写成回环 authority 就能放行」——都出自该项目 README 的表述。如果这个模型与你环境里栅栏的实际行为不一致,先查 DSH 官方文档再改配置。

适用场景与注意

适合:想把 DSH Web UI 暴露到内网或公网、又不想继续抄 token 的人;已经架好 NPM/Caddy/Traefik、被 /api 403 卡住的人;想用一个原生设置页管理密码与会话的人。

务必注意:

  1. 不要卸载、也不要禁用它。不要执行 dsh plugin --profile web remove @lvxianchao/dsh-web-guard;不要从 profile 的 package.json 里删掉 dsh.profile.bundles 中这一项;也不要在 cordis.patch.yml 里给它加 disabled: true。DSH 的「设置 → 插件」页是只读清单,只有状态标签,没有开关,误关基本发生在命令行。
  2. 要求 DeepSeek Harness 0.2.2-rc.1 或更新(engines.dsh: >=0.2.2-rc.1 <0.3.0),Node >=22.19,许可证 MIT。宿主版本不匹配时整个 bundle 会被跳过——这时是闸门不启动,应该升级 DSH,而不是删掉插件。
  3. 插件以当前 dsh 进程权限运行,装前应检查源码与许可证。
  4. 会话表会落盘到 <profile>/data/dsh-web-guard-sessions-<port>.json(0600,临时文件 + rename 原子写入),重启 DSH 不会把已登录浏览器踢下线——但重启不等于吊销,要全部吊销用设置页的「退出所有设备」。

如果不小心关掉了,恢复命令不依赖插件本身:

# 1) 重新装回(本地目录装的就用 file:)
dsh plugin --profile web add file:/path/to/dsh-web-guard
# 或:dsh plugin --profile web add github:lvxianchao/dsh-web-guard

# 2) 重启,3081 会重新监听
sudo systemctl restart dsh-web

如果当初是加了 disabled: true,把那行删掉再重启;密码哈希在 <profile>/data/dsh-web-guard.json,不丢。

真正的坑不在这两条命令,而在于此刻你已经连不上这台机器。闸门只代理 HTTP 入口,不碰 SSH;但如果远程访问都收在这一个域名上,插件一没,只剩一条路——一条不经过它的后路。至少准备一条:宿主控制台(PVE / ESXi / 云厂商 VNC)进容器或虚机执行上面两条命令,或一条独立 SSH 通道(内网直连、跳板机、VPN)。并且在今天、现在就验证它能用,然后把恢复命令抄一份到你能从外面打开的地方。

结尾

dsh-web-guard 的价值在于把「抄 token、改 Host、挖日志、明文 HTTP 报错」这四件事收敛成一个插件,代价是它成了唯一的入口——好处和风险都来自这一点。安装前先确认 DSH 版本够新,装后留一条不经过闸门的后路。

目录页:https://www.skillhub.cn/plugins/lvxianchao/dsh-web-guard
GitHub:https://github.com/lvxianchao/dsh-web-guard
npm:https://www.npmjs.com/package/@lvxianchao/dsh-web-guard

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜