Bundle
dsh-gateway-plugin
Access-password gateway plugin for DeepSeek Harness Web: every request on the gateway port requires authentication (no unauthenticated surface besides the login/setup page), first visit sets the password, and it can be rotated from the dedicated Security settings page.
- Source
- laoin114514
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 4 days ago
Readme
# 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 ...`)。
```sh
dsh plugin --profile web add github:laoin114514/dsh-gateway
```
推荐固定到具体 commit(防止仓库后续推送悄悄改变安装时拿到的代码):
```sh
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_...`(常见于之前失败安装留下的半装目录),先清理再重装:
```sh
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`)里覆盖:
```yaml
- 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')` 字段一致);请按机密文件的权限管理该文件。
- 会话在进程重启后全部失效(签名密钥随机生成),属有意行为。
## 开发
```sh
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、限流、密码轮换 |
Install
dsh plugin --profile web add github:laoin114514/dsh-gateway
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-gateway-plugin from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.