DSH Web 反向代理部署
DeepSeek Harness 反向代理:public-url、路径前缀与安全
- 作者
- DeepSeekAgent.io 编辑部
- 发布
- 更新
DeepSeek Harness Web 默认只在 loopback 地址提供明文 HTTP。这样的设计适合本机使用,却不能直接告诉远程浏览器“用户真正访问的是哪个 HTTPS 域名和路径”。v0.2.1-alpha.1 新增 --public-url,用于在反向代理、隧道或子路径部署中公告外部地址。
关键是把三个概念分开:监听地址、公告地址和可信 authority。它们分别解决网络可达、URL 生成和 API 放行,任何一个都不能替代另外两个。
最小启动方式
假设用户通过 https://app.example/ui/ 访问,而 DSH 仍在服务器本机监听:
dsh --profile web \
--public-url https://app.example/ui/ \
--trusted-host app.example \
--no-open
DSH 会把公告根规范化为以 / 结尾,并用它生成打印的启动 URL、DSH_WEB_URL 和 Web 界面定位。监听器本身仍提供 origin-root 路由,并不知道 /ui/ 的存在。
三个地址不要混在一起
| 层次 | 示例 | 作用 |
|---|---|---|
| DSH Listener | http://127.0.0.1:3080 | Host 实际监听,只允许本机代理访问 |
| Public URL | https://app.example/ui/ | 浏览器、界面和模型看到的外部入口 |
| Trusted Host | app.example | API 浏览器信任栅栏允许的 authority |
--public-url 只公告,不授予信任;漏掉 --trusted-host 时,页面可能加载,但 API 请求会返回 403。--trusted-host 也不是登录系统,它只放行 authority。真正的会话认证仍来自启动 URL 中的 Token 与后续签名 Cookie。
反向代理必须完成五件事
1. 保留浏览器可见的 Host
DSH 会把收到的 Host 与可信 authority 比较。代理不能把它改成 127.0.0.1:3080,应转发用户实际访问的 Host。
2. 剥离路径前缀
浏览器请求 /ui/api/...,后端必须收到 /api/...。proxy_pass 尾部斜线等实现细节决定前缀是否被正确移除。
3. 转发 WebSocket Upgrade
只转发普通 HTTP 会导致页面能打开、会话却无法实时连接。Upgrade 与 Connection 头必须到达监听器。
4. 改写 Cookie Path 与 Secure
后端签发 host-only、Path=/、不带 Secure 的 Cookie。子路径部署需要代理把 Path 改为 /ui/;外部链路为 HTTPS 时还要添加 Secure。
5. 把 /ui 重定向到 /ui/
带尾部斜线的 /ui/ 是启动 Token 换取 Session Cookie 的规范入口。后端看不到被剥离的外部前缀,无法替代理修复错误入口。
Nginx 配置骨架
下面只展示 DSH 所需的关键行为,证书、访问控制与限流应放入你的现有站点配置:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name app.example;
location = /ui {
return 301 /ui/;
}
location /ui/ {
proxy_pass http://127.0.0.1:3080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cookie_path / /ui/;
proxy_cookie_flags ~.* secure;
}
}
上线前不要只检查首页。至少验证:启动链接能换取 Cookie、新建 Session、发送 Prompt、工具流式输出、刷新后恢复以及 WebSocket 重连。
为什么不直接监听 0.0.0.0
官方 CLI 有意拒绝 --host 0.0.0.0。DSH Web 的设计是让 loopback Listener 留在可信边界内,由反向代理承担 TLS、外部域名、路径前缀和访问策略。
--public-url 和 --trusted-host 都不会保护监听端口。如果你用容器或端口映射把 3080 暴露到公网,攻击者可能绕过代理层的 TLS、身份验证与 Cookie 改写。正确做法是只允许本机或受控代理网络访问 Listener。
Cloudflare Tunnel 与其他隧道
临时隧道通常每次启动产生不同域名。--trusted-host 中不写端口的 authority 可以匹配不同端口,但域名改变时仍要同步新的 Public URL 与可信 Host。
使用 Cloudflare Tunnel 时,至少确认:
- Origin Service 指向
http://127.0.0.1:3080; - 浏览器 Host 被保留;
- WebSocket 已启用;
- 外层使用 HTTPS;
- Cloudflare Access 或其他入口认证不会破坏启动 Token 跳转;
- 不把启动 URL 中的 Token 写入公开日志、截图或分析平台。
常见故障定位
| 现象 | 优先检查 |
|---|---|
| 页面打开但 API 全部 403 | --trusted-host 是否与浏览器 Host 一致 |
/ui/ 能开,静态资源或 API 404 | 代理是否正确剥离路径前缀 |
| 页面加载后一直重连 | WebSocket Upgrade 与 Connection 头 |
| 登录后刷新又要求启动 Token | Cookie Path 是否仍为 /,Secure 是否正确 |
/ui 与 /ui/ 表现不同 | 是否配置裸挂载重定向 |
| 模型给出 localhost 链接 | --public-url 是否正确公告给 Web Surface |
安全检查清单
- Listener 只绑定 loopback,不直接暴露公网。
- 外部入口强制 HTTPS,并启用访问控制和速率限制。
--public-url使用用户真实访问的规范根,包含正确路径前缀。--trusted-host只列出需要的 authority。- 启动 Token 只交给预期用户,避免进入 Referer、日志和截图。
- Cookie Path 限制在挂载前缀,并在 HTTPS 链路添加 Secure。
- 对 Prompt、工具流、刷新恢复和 WebSocket 做完整验收。