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 Listenerhttp://127.0.0.1:3080Host 实际监听,只允许本机代理访问
Public URLhttps://app.example/ui/浏览器、界面和模型看到的外部入口
Trusted Hostapp.exampleAPI 浏览器信任栅栏允许的 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 头
登录后刷新又要求启动 TokenCookie Path 是否仍为 /,Secure 是否正确
/ui 与 /ui/ 表现不同是否配置裸挂载重定向
模型给出 localhost 链接--public-url 是否正确公告给 Web Surface

安全检查清单

  1. Listener 只绑定 loopback,不直接暴露公网。
  2. 外部入口强制 HTTPS,并启用访问控制和速率限制。
  3. --public-url 使用用户真实访问的规范根,包含正确路径前缀。
  4. --trusted-host 只列出需要的 authority。
  5. 启动 Token 只交给预期用户,避免进入 Referer、日志和截图。
  6. Cookie Path 限制在挂载前缀,并在 HTTPS 链路添加 Secure。
  7. 对 Prompt、工具流、刷新恢复和 WebSocket 做完整验收。

相关阅读

参考资料