🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

局域网网关 rice-awa/dsh-lan-gateway

DeepSeek Harnessv0.7.3
rice-awa6 天前有提交已提供中文文档扫描:含敏感能力

把 DeepSeek Harness 的 Web GUI 安全地开放到局域网或公网,自带密码鉴权,支持可选的 TLS。

38.8/100
综合分
8
GitHub 星标
441
npm 周下载
—/5
用户评分

数据截至 2026/10/31 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @riceawa/dsh-lan-gateway
npm 源 · @riceawa/dsh-lan-gateway 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么[目录]—
dsh 原生插件 · model
暂无时间戳·判据 →
装得上吗[实装验证]—
曾在 dsh 0.2.1-rc.5 下真实安装成功,但当前 dsh 已更新至 0.2.2-rc.1,本站尚未在新版下复验
L5 · 运行时冒烟等级说明 →
数据截至 2026/10/31·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/05·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 6 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包@riceawa/dsh-lan-gateway @ 0.7.3
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 03:45:09

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 43 处,展示前 36 条)
  • 发起网络请求
    lib/index.js:493
    const request = http.request({
  • 读取本地文件
    lib/index.js:979
    const raw = readFileSync(join(dir, STATE_FILENAME), "utf8");
  • 写入本地文件
    lib/index.js:1000
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/index.js:1003
    writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
  • 写入本地文件
    lib/index.js:1004
    renameSync(tmp, target);
  • 写入本地文件
    lib/index.js:1006
    chmodSync(target, 384);
  • 发起网络请求
    lib/index.js:1404
    const proxyReq = http.request({
  • 发起网络请求
    lib/index.js:1458
    const proxyReq = http.request({
  • 写入本地文件
    lib/index.js:1736
    writeFileSync(path, content, { mode: 384 });
  • 写入本地文件
    lib/index.js:1738
    chmodSync(path, 384);
  • 读取本地文件
    lib/index.js:1751
    if (existsSync(certPath) && existsSync(keyPath)) try {
  • 读取本地文件
    lib/index.js:1752
    const cert = readFileSync(certPath, "utf8");
  • 读取本地文件
    lib/index.js:1753
    const key = readFileSync(keyPath, "utf8");
  • 读取本地文件
    lib/index.js:1823
    if (!existsSync(certPath) || !existsSync(keyPath)) return void 0;
  • 读取本地文件
    lib/index.js:1824
    const cert = readFileSync(certPath, "utf8");
  • 发起网络请求
    lib/client.js:369
    fetch("/lan-gateway/config").then(async (response) => {
  • 发起网络请求
    lib/client.js:448
    const response = await fetch("/lan-gateway/password", {
  • 发起网络请求
    lib/client.js:485
    const response = await fetch("/lan-gateway/config", {
  • 发起网络请求
    src/gateway.ts:554
    const proxyReq = http.request({
  • 发起网络请求
    src/gateway.ts:632
    const proxyReq = http.request({
  • 读取本地文件
    src/state.ts:161
    const raw = readFileSync(join(dir, STATE_FILENAME), 'utf8')
  • 写入本地文件
    src/state.ts:186
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    src/state.ts:189
    writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 })
  • 写入本地文件
    src/state.ts:190
    renameSync(tmp, target)
  • 写入本地文件
    src/state.ts:193
    chmodSync(target, 0o600)
  • 写入本地文件
    src/tls.ts:38
    writeFileSync(path, content, { mode: 0o600 })
  • 写入本地文件
    src/tls.ts:40
    chmodSync(path, 0o600)
  • 读取本地文件
    src/tls.ts:57
    if (existsSync(certPath) && existsSync(keyPath)) {
  • 读取本地文件
    src/tls.ts:59
    const cert = readFileSync(certPath, 'utf8')
  • 读取本地文件
    src/tls.ts:60
    const key = readFileSync(keyPath, 'utf8')
  • 写入本地文件
    src/tls.ts:68
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    src/tls.ts:101
    mkdirSync(dir, { recursive: true })
  • 读取本地文件
    src/tls.ts:124
    if (!existsSync(certPath) || !existsSync(keyPath)) return undefined
  • 读取本地文件
    src/tls.ts:125
    const cert = readFileSync(certPath, 'utf8')
  • 读取本地文件
    src/tls.ts:126
    const key = readFileSync(keyPath, 'utf8')
  • 发起网络请求
    src/upstream-session.ts:161
    const request = http.request({
扫描于 2026/11/5(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/cordis-plugin-loader@deepseek-ai/cosmokit@deepseek-ai/dsh-brand@deepseek-ai/dsh-agent@deepseek-ai/dsh-ptc-runtime@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-sandbox-policy@deepseek-ai/dsh-session@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-user-approval@deepseek-ai/dsh-cordis-client-runner
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

1 张 · 点击查看大图

功能亮点

  • 开放WebGUI到局域网
  • 开放WebGUI到公网
  • 密码鉴权与TLS
v0.6.02026-09-30
· 变更内容 · 修复:适配 dsh 0.1.7 的设置重写(移除 settingsScope、易变配置引用、entry-id 写入) · 修复:恢复 0.1.7 上的设置卡片(plugins.item 槽),发布 0.6.0 · 新贡献者 · @xain 首次贡献于 完整变更日志:
v0.5.52026-09-19
· 变更内容 · 修复:架构复审修复(C1–C6 / D1–D14),发布 0.5.5 完整变更日志:
v0.5.42026-09-19
· What's Changed · fix: 0.5.4 — 归属路径归一化、中继 Cookie 权威性、响应头过滤 · fix: 0.5.4 — 逐会话登出、双栈监听、证书到期换发、尾斜杠归一化 · New Contributors · @rice-awa made their first contribution in Full Changelog:
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-lan-gateway — LAN / 公网网关插件 dsh web 明确拒绝 --host 0.0.0.0,以免把远程代码执行暴露到网络。本插件的做法是让 dsh 继续只绑 127.0.0.1,另起一个反向代理监听未指定地址(双栈,IPv4 与 IPv6 客户端都可接入),转发到 loopback 端口并改写 Host / Origin。 默认拒绝:loopback、LAN、公网三种来源都要先在网关登录页取得 HMAC 会话 cookie,LAN 免密需要显式打开 lanPasswordless,默认关闭。底座要求 dsh 0.2.0 / 0.2.1:engines.dsh 与 @deepseek-ai/dsh- peer 范围同时声明这两条线,无论底座按哪个字段判定兼容性都能通过;0.1.7 及更早版本不再支持。网关在进程内中继一条共享上游会话,上游自身的授权仍然把关每个请求,网关只决定谁可以使用这条会话。 dsh 0.2.1 的 --host 可以绑定单个本机网卡地址(通配地址仍被拒绝),--tls-cert / --tls-key 可直接提供 HTTPS。网关的定位不变:它负责通配监听、登录门禁与自签名证书,并始终向 127.0.0.1 转发。若改用 dsh 自己的 --host 直接绑定网卡,请关闭网关——两者不要同时使用。
展开完整文档(README 剩余 1 节)
dsh-lan-gateway — LAN / 公网网关插件

dsh web 明确拒绝 --host 0.0.0.0,以免把远程代码执行暴露到网络。本插件的做法是让 dsh 继续只绑 127.0.0.1,另起一个反向代理监听未指定地址(双栈,IPv4 与 IPv6 客户端都可接入),转发到 loopback 端口并改写 Host / Origin。

默认拒绝:loopback、LAN、公网三种来源都要先在网关登录页取得 HMAC 会话 cookie,LAN 免密需要显式打开 lanPasswordless,默认关闭。底座要求 dsh 0.2.0 / 0.2.1:engines.dsh 与 @deepseek-ai/dsh- peer 范围同时声明这两条线,无论底座按哪个字段判定兼容性都能通过;0.1.7 及更早版本不再支持。网关在进程内中继一条共享上游会话,上游自身的授权仍然把关每个请求,网关只决定谁可以使用这条会话。

dsh 0.2.1 的 --host 可以绑定单个本机网卡地址(通配地址仍被拒绝),--tls-cert / --tls-key 可直接提供 HTTPS。网关的定位不变:它负责通配监听、登录门禁与自签名证书,并始终向 127.0.0.1 转发。若改用 dsh 自己的 --host 直接绑定网卡,请关闭网关——两者不要同时使用。

插件另外提供两项功能:

- 不安全源 UUID shim:网关以纯 HTTP 的局域网地址服务页面,浏览器视其为不安全源,不提供 crypto.randomUUID。client bundle 在页面加载早期补一个基于 getRandomValues 的实现,工作区才能正常打开。
- TLS:自动生成并持久化的自签名证书,或者挂载自行签发的 PEM。自签名证书首次访问会有浏览器警告,属预期行为。

安装

已发布到 npm,安装的是预构建产物,不需要 allowBuilds 授权。可将下面这段话交给你的 agent:

帮我安装 dsh 插件 @riceawa/dsh-lan-gateway,遵循 https://github.com/rice-awa/dsh-lan-gateway/blob/main/INSTALL.md

也可以手动执行:

dsh plugin --profile web add @riceawa/dsh-lan-gateway

dsh plugin ... add 会把参数转发给 profile 目录里的 pnpm。npm 包自带 lib/,不会触发 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED。如果仍然报错,把报错条目写进 ~/.dsh/profiles/web/pnpm-workspace.yaml 的 allowBuilds 后重试,完整步骤见 INSTALL.md。

从源码构建:

git clone https://github.com/rice-awa/dsh-lan-gateway.git
cd dsh-lan-gateway
pnpm install
pnpm build # host → lib/index.js
pnpm build:client # client → lib/client.js
pnpm test # 216 项

仓库里还有一个 lan-gateway 技能,安装后可直接在 dsh 对话里说「设置网关密码为 …」「开启远程访问」,agent 会调用 lan_gateway 工具完成,密码以参数传入,不写入配置,也不回显。改密码也可以不走模型:在本机打开 Plugins 页的「LAN 网关」卡片,直接填两次新密码覆盖(见下「登录密码」)。安装方式见 INSTALL.md。

如需在手机 / 平板上访问,可另外安装 dsh-web-mobile 做移动端 UI 适配:

dsh plugin --profile web add github:mexiaosqwq/dsh-web-mobile

用法

lan_gateway enable # 开启网关(需先满足启动条件,否则给出迁移文案) lan_gateway status # 端口 / 目标 / 密码 / 会话 epoch / 已登出会话数 / 中继状态 / 入口加密方式 / 上次错误 lan_gateway set-password # 设置登录密码(≥8 位,改动会让所有已签发会话立即失效) lan_gateway rotate-secret # 轮换会话密钥,作废全部登录 cookie 与已建立的 WebSocket lan_gateway tls-regenerate # 换发自签名证书(tlsMode=self-signed 时) lan_gateway disable # 关闭

lan_gateway 是模型可调用的工具,上述命令无需手动执行。直接在对话里说「查看网关状态」「设置网关密码为 ……」即可,密码以参数传给模型,不会写入任何配置文件。改密码也可以完全不经模型:在本机 Plugins 页的插件卡片里直接改(见下「登录密码」)。

配置

所有可调项都写在本插件自己的 profile 条目里(dsh ≥ 0.1.7 起,设置写入按条目 id 寻址;此前的 lan-gateway 用户设置命名空间已随 settingsScope 一起移除)。打开侧边栏的 Plugins 页 →「已安装」→ @riceawa/dsh-lan-gateway:本插件的配置就渲染在这个包自己的页面上——挂的是 dsh 给「bundle 自己的配置」声明的 plugins.bundle.config 槽(按包名 @riceawa/dsh-lan-gateway 挂载,与 dsh-mnemon、dshmarket 等插件同一套机制),页面标题、图标与面包屑由 Plugins 页自己绘制。保存即生效,监听器会按新配置自动重启。宿主机上用 127.0.0.1 / localhost 打开网关地址时该页面同样可用(网关只对宿主机本机浏览器放行它的管理前缀,见「安全模型」),桌面版(dsh-app://app)里也照常读写(见 0.7.1);用局域网 IP、域名或从别的机器访问时页面读不到配置,改用 lan_gateway 工具。注册只在底座确实 served 本插件条目时发生——没有 Loader 条目(写入必然 409)时页面不会出现。下表既是卡片字段,也是配置键;页面顶部另有一栏「登录密码」,改密码不走配置表(见下「登录密码」):

| 键 | 默认值 | 说明 |
| enabled | false | 是否在启动时监听网络端口 |
| gatewayPort | 3081 | 网关监听端口(未指定地址 / 双栈) |
| dshTargetPort | 跟随 ctx.webServer.port | 转发到的 dsh loopback 端口 |
| lanCidrs | RFC1918 + link-local(见下) | 视为 LAN 的网段,仅在 lanPasswordless 开启时用作豁免匹配集 |
| lanPasswordless | false | LAN/loopback 来源跳过网关登录页(上游会话中继仍把关) |
| cookieMaxAgeDays | 7 | 会话 cookie 有效期(天) |
| cookieName | dsh_gw_auth | 会话 cookie 名,不进卡片 |
| tlsEnabled | false | 是否以 HTTPS 提供服务 |
| tlsMode | self-signed | self-signed 自动生成 / custom 用自己的证书 |
| tlsSelfSignedHosts | localhost | 自签名证书的 SAN(逗号分隔的域名 / IP)。仅影响下次换发,见下「证书有效期」 |
| tlsCertPath | — | custom 模式:PEM 证书(或证书链)绝对路径 |
| tlsKeyPath | — | custom 模式:PEM 私钥绝对路径 |
| tlsCertMaxAgeDays | 825 | 自签名证书有效期(天),见下「证书有效期」 |
| allowInsecurePlaintext | false | 允许明文 HTTP 监听(见下「入口加密」) |
| trustedTerminator | — | 声明一个受信 TLS 终止代理标识,视为加密入口(如 nginx)。注意登录限流的键,见下「登录限流」 |
| secureCookies | 自动 | 会话 cookie 的 Secure 属性显式开关,默认按 tlsEnabled 或 trustedTerminator 推断(见下) |

默认 lanCidrs:10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、169.254.0.0/16。IPv6 的 fe80::/10(link-local)与 127.0.0.0/8、::1 归为 LAN/loopback。

配置里残留 authRequired: false(v0.4 及更早的写法)会被启停守卫拒绝并提示迁移,不会静默降级成免密。

登录密码

卡片顶部就是「登录密码」栏:一个「已设置 / 未设置」状态,两个密码框(新密码、再次输入),点「修改密码」直接覆盖原密码。

- 不回显旧密码。 界面只报告密码是否存在;卡片读不到旧密码。state.json 里只有 scrypt 哈希与盐,/lan-gateway/password 也从不把哈希、盐或长度返回给浏览器。
- 不需要旧密码。 该路由与配置路由共用同一道围栏:只接受本机 loopback 的同源请求(Host 必须是回环、跨站请求拒绝、带 Origin 时必须与 Host 匹配;不带 Origin 的写操作放行——桌面版的 dsh-app://app 桥会剥掉这个头,dsh 自己的路由规则也不要求它)。经网关访问时,只有宿主机本机浏览器(回环来源 + 回环地址)能被放行到这道围栏,远程浏览器仍是一律 403。能通过这两道门的本地用户本来就能读 ~/.dsh。
- 改完即生效。 写盘后递增会话代次:旧密码立即失效,所有已登录会话与已建立的 WebSocket 全部作废,各来源需要重新登录。
- 只设置,不清空。 清空密码会按设计停掉监听器(没有密码不允许监听),卡片不做这件事;要清空请用 lan_gateway set-password 并留空密码。
- 密码不是配置键。 它不写进 profile 条目,也不会出现在 --dump-config 里;保存配置字段动不到它,改密码也不会碰你尚未保存的字段草稿。

入口加密

网关默认拒绝纯明文监听,以下三种方式任选其一方可启动:

1. 启用 TLS,tlsEnabled: true。推荐,自签名或 custom 证书均可。
2. 声明由可信反向代理终止 TLS:

- id: dsh-lan-gateway
config:
enabled: true
gatewayPort: 8080
trustedTerminator: nginx # nginx 以 HTTPS 对外,再转发回本端口

3. 显式接受明文,风险自担,密码与会话会在网内明文传输:

- id: dsh-lan-gateway
config:
enabled: true
gatewayPort: 3081
allowInsecurePlaintext: true

使用自有证书(例如 Let's Encrypt 签发的 PEM):

- id: dsh-lan-gateway
config:
tlsEnabled: true
tlsMode: custom
tlsCertPath: /etc/letsencrypt/live/example.com/fullchain.pem
tlsKeyPath: /etc/letsencrypt/live/example.com/privkey.pem

自签名证书在首次启用 TLS 时生成一次,写入 ~/.dsh/lan-gateway/tls/(selfsigned.crt / selfsigned.key,0600),之后重启复用。更换证书用 lan_gateway tls-regenerate,它会换掉密钥并热重启监听器。已到期的证书在启动时自动换发,并记一条 warning。修改 tlsSelfSignedHosts 或 tlsCertMaxAgeDays 只影响下次换发:已有证书沿用至到期,保存时不会被悄悄替换。

证书有效期

默认有效期 825 天。常见的 398 天上限只约束链到平台预装根 CA 的证书,Apple 明确豁免「由用户或管理员添加的根 CA」签发的证书(support.apple.com/en-us/102028),而自签证书必然是这样一条根——没有任何系统预装它。825 天则是 Apple 对 TLS 服务器证书给出的上限(support.apple.com/en-us/103769),默认值取在该上限之内。

自签证书要么被点击通过、要么被手工信任,因此缩短有效期没有收益,每次到期反而要重新信任一次。需要更短的窗口时用 tlsCertMaxAgeDays 自行调整。

监听器自身是 HTTPS 时,网关的响应(登录页 / 重定向 / 拒绝)带 HSTS。

关于 Secure cookie

启用 TLS 或声明受信终止代理后,登录 cookie 自动带 Secure。但「声明了受信代理」只说明网关前方存在一个代理,不说明浏览器到代理这一段是加密的。

如果该代理只做明文鉴权、浏览器以 http:// 访问(代理再以明文转发回本端口),自动推断会把 Secure 加上,而浏览器拒收明文 http 上的 Secure cookie。结果是密码校验通过、cookie 无法保存,每次都被重定向回 /__login,无限循环。这种部署需要显式关闭:

- id: dsh-lan-gateway
config:
enabled: true
gatewayPort: 8080
trustedTerminator: nginx
secureCookies: false # 浏览器 → nginx 是明文 http,不能带 Secure

lan_gateway status 会如实报告实际生效的属性,以及声明的代理属于 TLS 还是明文入口。设置页里对应「自动 / 始终 Secure / 不加 Secure」三档。

登录限流

登录尝试以 TCP 源地址为键限流,每分钟 5 次。X-Forwarded-For 与 Forwarded 在转发前一律删除,也不参与限流取键——它们可由客户端任意伪造,用它们作为键等于把限流关掉。

代价是声明 trustedTerminator 之后,所有浏览器请求都来自代理这一个地址,5 次/分钟变成整个部署共享的额度,而非每客户端 5 次。这一耦合没有便宜的修法:要么接受它(把上限视为抗暴力破解的下限而非每用户配额),要么让代理直连、由网关自己面对客户端。当前不接受按 X-Forwarded-For 分桶。

注意 secureCookies: false 说的是浏览器到入口这一段是明文,网关登录密码和会话 cookie 会在这一段明文传输。这与 allowInsecurePlaintext 描述的不是同一段链路:后者指代理到网关之间不加密,前者指浏览器到代理之间不加密。只有当代理本身已经对用户完成鉴权、且可以接受这段明文时,才应这样配置。

安全模型

- 来源分级只认 socket.remoteAddress(IPv4-mapped IPv6 会先解包),分 loopback / lan / internet 三档,绝不信任 X-Forwarded-For。分级本身不授予任何访问,每一档默认都要出示有效网关会话,否则 302 到 /__login。
- LAN 免密是显式 opt-in。lanPasswordless: true 只让命中 lanCidrs 或 loopback 的来源跳过网关自己的登录页;受支持的底座(0.2.0 / 0.2.1)上游会话仍把关每个请求。底座没有浏览器会话认证时这个开关拒绝启用,否则等同于把 QVD-2026-57410 原样恢复。
- 共享上游会话中继。dsh 0.2 不再信任回环 Host,要求出示 HMAC 签名的 dsh-auth- cookie。插件经 connection 服务拿到启动令牌,在回环传输上做一次浏览器等价的令牌换取,取得 cookie 后中继到每个转发请求;上游一旦 401 就丢弃这条会话并重新换取。这仍是「单密码 = 单操作者」:通过网关登录的用户共用同一条上游会话,持钥的上游才是真正的授权主体。
- 登录页。/__login 由网关独占、不转发。密码以 scrypt 校验,每写一次重新加盐;登录尝试按来源限流(5 次 / 分钟)。
- 会话 cookie 采用 payload.signature 结构(HMAC-SHA256),带撤销 epoch 与逐会话 id,HttpOnly; SameSite=Strict。登出只撤销当前这条会话:它建立的 WebSocket 一并断开,其他设备不受影响。改密、清密、rotate-secret 会递增 epoch,作废全部已签发 cookie 并断开全部已建立的 WebSocket,客户端需要重新登录。清空密码会直接停止监听。
- 管理面不外泄。/lan-gateway/(含配置路由与改密路由)由网关独占:唯一放行的是宿主机本机浏览器,即「TCP 来源为 loopback 且 浏览器地址栏写的是回环权威(127.0.0.1 / localhost / ::1)」的请求,其余一律 403 不转发,远程访问者无法借网关改写 Host 触及本机 loopback 的配置接口。两个条件缺一不可:来源一项远程无法伪造,地址一项则挡住受信 TLS 终止代理的部署——那里所有请求的 TCP 来源都是代理自己的回环地址,只有回环权威仍被拒。放行的请求照样要过会话门与 CSRF 围栏,再以改写后的 loopback Host 交给插件自己的回环同源围栏。升级请求(WebSocket)不在此列,一律拒。原生 /lan-gateway/config 只应答回环 Host 且同源的请求。远程管理走 lan_gateway 工具。
- CSRF 围栏(HTTP 与 WebSocket)。网关把 Origin 改写回 loopback,会蒙蔽 dsh 自身的 CSRF 防线,所以在改写前对每个转发请求自检:sec-fetch-site: cross-site 直接拒;Origin 必须匹配访问者实际使用的网关权威来源;状态变更方法与 WebSocket 升级请求必须携带同源 Origin,否则 403。
- 未设置密码时拒绝监听,与来源无关。

登录页

远程来源打开 http://<主机>:3081/ 时先看到网关自带的登录表单,输入正确密码后签发会话 cookie 并跳转回 /。

UUID shim

网关以 http://<LAN-IP>:3081 服务页面,浏览器视其为不安全源,crypto.randomUUID()(仅安全源可用)为 undefined,于是每次 RPC id 铸造都抛 crypto.randomUUID is not a function,工作区无法打开。

client bundle 在模块级给 Crypto 原型补一个基于 crypto.getRandomValues() 的 randomUUID(RFC 4122 v4,getRandomValues 在所有源都可用)。它在浏览器求值时就执行,早于任何官方代码铸造 id,所以对官方所有调用点(含以后新增的)一律生效,无需修改 DSH 源码。安全源和 Node ≥ 19 下是 no-op,不影响任何行为。

开发

pnpm test # 219 项
pnpm typecheck # tsc 双端(host + client)

✓ tests/gateway.test.ts (40) 分类 / HMAC cookie / epoch / 逐会话撤销 / 密码状态 / 限流 ✓ tests/start-guard.test.ts (21) fail-closed 启动守卫 / 配置路由回环围栏(含桌面桥形状)/ Secure cookie 属性推断(含 null 清除路径) ✓ tests/request-policy.test.ts (50) 判定缝纯函数:路径归一化 / 归属前缀 / 同站与登录围栏 / 回环权威(Host)判定 / 两个方向的头部变换 ✓ tests/upstream-session.test.ts ( 8) 真实回环令牌换取:cookie 名匹配 / 拒绝后重换 / invalidate 重获取 / 日志播报 / 保住已持有会话 ✓ tests/x509.test.ts ( 6) 自签名证书 DER/SAN/签名/TLS 握手 ✓ tests/tls.test.ts ( 9) 证书持久化 / 到期换发 / 重生成 / 自定义证书加载 ✓ tests/uuid-shim.test.ts ( 3) 不安全源补丁 / 安全源 no-op / v4 正确性 ✓ tests/settings-card.test.ts (18) 设置页字段编解码(三态 auto ↔ false 不可混淆)/ 卡片注册槽与条目 id 契约 / 密码草稿闸门(长度与确认)/ 密码状态徽标三态(缺字段 = 未知,不是「未设置」)/ 「网关自己拒绝」与其它失败的文案分流 ✓ tests/integration/gateway.test.ts (30) 真实网关端到端:全来源登录 / LAN 豁免 / 跨站 403 / 升级拒绝 / cookie 属性 / epoch 撤销 / 逐会话登出 / 尾斜杠 / IPv6 / 会话中继 / 管理面只对本机浏览器放行(来源与 Host 两项都要满足) ✓ tests/integration/management-plane.test.ts (26) 真实 apply():工具与卡片交替启停 / 未编辑字段与未知键保留 / 清空后继承 / 拒绝不可启动配置 / 桌面桥形状的保存(无 Origin)/ 卡片改密路由(覆盖原密码 / 递增代次 / 不返回哈希 / 不给清空 / 无 settings 也生效) ✓ tests/integration/session-races.test.ts ( 8) 改密落在登录与握手途中的竞态 / 上游非 101 应答

发布

推 v tag 触发 .github/workflows/release.yml:校验 tag 与 package.json 版本一致,跑 typecheck 和 test,发 npm,然后建 GitHub Release 并附上 pnpm pack 的 tgz。npm 侧走 Trusted Publishing(GitHub OIDC),仓库里不需要 NPM_TOKEN secret。lib/ 被 gitignore,但 prepack 会构建,所以发布产物里始终有编译结果。

首次启用要在 npm 包页面的 Settings → Trusted Publisher 配一次:

| 字段 | 值 |
| Organization or user | rice-awa |
| Repository | dsh-lan-gateway |
| Workflow filename | release.yml |
| Environment | 留空 |

配好之前 tag 推送会在 npm publish 一步以 403 失败(fail-closed,不会留下半个 Release)。改完不用重新打 tag,重跑那次 run 即可。

本地手动发布走 checkout 里的 .npmrc token(该文件不入库):

pnpm install --frozen-lockfile
pnpm typecheck && pnpm test
npm publish --access public # prepack 自动构建 lib/
git tag -a v0.6.0 -m "…" && git push origin v0.6.0
gh release create v0.6.0 --generate-notes ./*.tgz # 可选:Release + tgz 附件

安全评估

0.5.0 的默认拒绝模型源自针对 QVD-2026-57410(DSH Web API 的 Host 信任缺陷)的加固,相关文档在 docs/security/:

- LAN 网关安全评估:0.4.0 时代的 F1–F5 审计快照与 13 个隔离观察,顶部标注了 0.5.0 的修复状态。
- QVD-2026-57410 修复方案:方案全文 + §15 实施状态。
- 上游研究:公开通告、上游提交与版本核对。
- 0.5.4 复审与修复清单:0.5.3 的实现细节复审(G1–G12)与修复记录。

架构层面的复审另见 docs/review/:

- 架构复审(2026-09-19):针对 0.5.4 的 C1–C6 结构候选与 D1–D14 缺陷清单,0.5.5 已全部关闭。文档记录的是复审当时的状态,不是待办列表。

许可

MIT

各版本改了什么见 CHANGELOG.md。
预览分享卡片长什么样 ↓rice-awa/dsh-lan-gateway
分享 · DeepSeek Harness Hub
rice-awa/dsh-lan-gateway

把 DeepSeek Harness 的 Web GUI 安全地开放到局域网或公网,自带密码鉴权,支持可选的 TLS。

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部