dsh-gateway
No description
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 23, 2026
- Updated
- Aug 23, 2026
Introduction
dsh-gateway-plugin
DeepSeek Harness Web 的访问密码网关插件:网关端口上不存在任何未认证可达的内容面——除登录/首次设置页外,一切请求(页面、静态资源、/api、WebSocket 升级)必须先通过验证;首次使用在登录页直接设置密码;密码可在设置页独立的"安全"页中修改。
为什么要用网关,而不是改 harness
DeepSeek Harness 的 Web 应用有意不提供鉴权(webserver 与 client-connection 包的已知限制里明确写出):官方 web 启动器甚至拒绝 --host 0.0.0.0,/api 的 browser-trust fence 只是防 DNS 重绑(注释白纸黑字:explicitly not authentication)。官方推荐的加固方式就是在前方放置真正的反向代理。
本插件把这道"反向代理"以 harness 插件形式实现,直接装进 profile:
浏览器 ──> dsh-gateway(自己的监听端口,唯一对外入口)── 反代 ──> harness webserver(保持 127.0.0.1 回环绑定)
├─ 未认证的任何请求(页面/静态/API/WS)──> 拒绝:页面 302 到 /gateway/login,/api 401
├─ /gateway/login GET ──> 唯一未认证放行面:已配置密码 → 登录页;未配置 → 首次设置密码页
├─ /gateway/login POST ──> 密码校验(常量时间)→ 签发 HttpOnly 签名会话 cookie
├─ /api/* + /api/events.* WebSocket ──> 会话 cookie 或 Authorization: Bearer <密码>
└─ 认证后全部反代到 harness(改写 Host/Origin 使内部信任围栏放行)
安全要点:
- 绝对门禁:网关监听端口上只有
/gateway/login(GET 页面 + POST 登录/设密)是未认证可达的;没有 health 端点、没有其他例外。未通过验证的请求什么内容都拿不到(页面与静态资源 302 到登录页,/api直接 401,WebSocket 升级拒绝)。 - 首次设置密码:部署还没有密码时,登录页呈现"设置访问密码"表单(两次输入),首个访问者设置成功后立即获得会话——这是唯一的引导信任路径。之后所有人用该密码登录。
- 修改密码:设置 → 安全(独立页面,不在"插件配置"里)→ 当前密码 + 新密码 ×2;旧密码在 Host 侧校验。修改成功后所有已登录会话立即失效(会话签名密钥轮换)。
- 防爆破:密码比对为 SHA-256 后
timingSafeEqual(恒定时间);登录失败按来源地址限流(默认连续 5 次失败冷却 30 秒)。 - 会话令牌:HMAC-SHA256 签名的无状态 cookie(
HttpOnly、SameSite=Lax),签名密钥每次进程启动随机生成——重启即全员重新登录,有意 fail-closed。 - 信任边界:代理改写 Host/Origin 为回环上游,harness 内部围栏与其回环受限特权方法(
settings.*、credentials.*等)接受请求;harness 本身应保持绑127.0.0.1(默认如此,且官方禁止0.0.0.0)。边界说明:dsh 本机回环端口上的直连等同于本机管理员(他们本就能读磁盘、改配置),不属于远程攻击面。
从 GitHub 安装
前提:dsh CLI 已安装(或用源码检出,从仓库根执行 pnpm dsh ...)。
dsh plugin --profile web add github:laoin114514/dsh-gateway
推荐固定到具体 commit(防止仓库后续推送悄悄改变安装时拿到的代码):
dsh plugin --profile web add github:laoin114514/dsh-gateway#<commit-sha>
构建产物已经随仓库提交(lib/ 目录),git 安装拉取后无需运行任何构建脚本、无需放行 build 权限,安装即用。首次安装/升级后启动 dsh web:
dsh-gateway: http://127.0.0.1:3088 (gateway, password required) -> harness http://127.0.0.1:3080
打开网关地址(而不是 harness 地址)使用。首次访问时登录页会引导你设置访问密码;之后每次访问输入该密码。
安装失败排查
如果 dsh plugin add github:... 报 ERR_PNPM_ENOENT ... importPackage ..._tmp_...(常见于之前失败安装留下的半装目录),先清理再重装:
dsh plugin --profile web remove dsh-gateway-plugin # 或手动删除
# 删除 $DSH_HOME/profiles/web/node_modules/dsh-gateway-plugin 后:
rm -rf "$DSH_HOME/profiles/web/node_modules/dsh-gateway-plugin"
dsh plugin --profile web add github:laoin114514/dsh-gateway
暴露到网络
默认网关只绑本机(gatewayHost: '127.0.0.1')。在 profile 的 cordis.patch.yml($DSH_HOME/profiles/web/cordis.patch.yml)里覆盖:
- id: dsh-gateway
config:
gatewayHost: '0.0.0.0'
源码检出下的本地开发
依赖本仓库源码联动时(改 harness 本地包),在仓库根执行 pnpm dsh web --patch <dsh-gateway>/cordis.dev.yml --no-open --port 3082(注意:该 overlay 方式下浏览器侧的"安全"页不会加载——只有 profile 行才能被客户端模块系统发现)。
配置(cordis.patch.yml 中 dsh-gateway 行)
| 字段 | 默认 | 说明 |
|---|---|---|
gatewayHost | '127.0.0.1' | 网关监听地址;对外暴露设为 '0.0.0.0'(harness 本体仍保持回环) |
gatewayPort | 0 | 网关监听端口;0 由系统分配(启动日志打印实际地址) |
sessionTtlHours | 24 | 会话 cookie 有效期(小时);密码修改或进程重启也会使所有会话失效 |
minKeyLength | 8 | 密码最短长度 |
maxLoginFailures | 5 | 连续失败多少次后进入冷却 |
loginCooldownSeconds | 30 | 冷却窗口(秒) |
accessKey | (空) | 初始密码;为空即进入"首次设置"模式(此时配置此字段等价于预先指定密码)。设置服务就绪时该值作为组合 base 层,可被安全页覆盖 |
密码本身存放在设置命名空间 dsh-gateway(字段 accessKey,用户设置文件),声明为 role('secret'),任何 wire 响应都不会携带它。
已知取舍
- 不提供 TLS:如需公网/跨网段传输,请在网关前再放一个终结 TLS 的反向代理,或把网络限制在内网。
- 密码以明文存于用户设置文件(与 harness 其他
role('secret')字段一致);请按机密文件的权限管理该文件。 - 会话在进程重启后全部失效(签名密钥随机生成),属有意行为。
开发
pnpm install && pnpm run build # esbuild 打包 + 两个 tsc 声明产物
pnpm test # node:test 集成冒烟(真实 Cordis Context + 假上游)
pnpm run typecheck
产物:
lib/index.js— Host 侧网关插件(ESM,仅@deepseek-ai/*保持外部解析)lib/client.js— 浏览器侧"安全"设置页(dsh 客户端模块系统的 lazy-CJS factory 格式)lib/types/— 两侧的声明文件
结构
| 文件 | 职责 |
|---|---|
src/index.ts | 网关插件本体:独立监听、反代、WebSocket 隧道、登录/设密/改密端点、设置命名空间接线 |
src/session.ts | 签名会话 cookie(签发/校验/轮换签名密钥) |
src/key.ts | 恒定时间密码比对 |
src/lockout.ts | 登录失败的按地址限流 |
src/pages.ts | 登录/首次设置页 HTML(零外部资源、内联 CSS/JS) |
src/client/ | "安全"设置页(settings.section 槽位,id security) |
cordis.patch.yml | bundle 层:插入 dsh-gateway 行(Host 插件 + dsh.client 浏览器贡献) |
cordis.dev.yml | 本地开发 overlay(file:// 行,直接 --patch 使用) |
tests/gateway.smoke.mjs | 集成冒烟:首次设密、无认证面断言、302/401、cookie 会话、Bearer、限流、密码轮换 |