通过反代访问提示大部分接口返回403 #653
Replies: 5 comments
|
同样的现象。服务器端口是不对外开放的,反代域名访问无法使用,大部分接口403 |
|
Status Assistant Message |
|
配置api key后,没法正常使用 |
|
我们遇到过完全一样的问题,已解决,分享下根因和方案: 根因dsh 的信任围栏按 最常见的 Host 头丢失原因:nginx 的 修复
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 3600s; # SSE 需要
}
验证方法改完后用 curl 模拟浏览器请求测一个特权接口: curl -i -X POST -H "Content-Type: application/json" \
-H "Origin: https://你的域名:端口" \
-d '{"type":"client-request","rpcId":"1","method":"pluginInventory/list","payload":{"args":{}}}' \
https://你的域名:端口/api/pluginInventory/list返回 200 即修复;仍 403 就抓一下反代转发后的实际请求头( 另外 @kidd-cn 的"配置 API key 后 DeepSeek API 请求失败"看起来是另一类问题(出站请求失败,与 403 无关),建议单独排查网络/代理出口。 |
|
我在昨天通过AI解决了这个问题,是通过修改源代码实现的:
# 反向代理站点部分接口返回 403 问题解决方案
## 一、问题背景
宝塔面板中的一个**反向代理类站点**(下文以「目标站点」代称,其域名为示例值 `example.com`,实际部署时替换为真实域名),浏览器访问时部分 API 接口返回 `403 Forbidden`。
受影响接口(均为 POST 请求):
- `/api/host.describe`
- `/api/settings.describe`
- `/api/llm.providers`
- `/api/agentPreset.list`
站点首页与静态资源访问正常,仅接口请求失败。
## 二、架构说明
目标站点为反向代理架构,nginx 将请求转发至本机上游应用:
```
浏览器 ──> nginx(绑定域名 example.com)──> 127.0.0.1:3080(上游 Web 应用)
```
## 三、根因分析
经排查,`403` **并非 nginx 产生,而是上游应用自身返回的**(直连 `127.0.0.1:3080` 可复现同样的 `forbidden` 响应)。
**核心原因**:上游应用内置了「浏览器信任围栏」,对每个 `/api` 请求执行两项校验:
1. **Host 校验**:请求 `Host` 头必须为回环地址(`127.0.0.1`)或已配置的 `trustedHosts` 域名;
2. **同源校验**:若请求携带 `Origin` 头(浏览器请求必然携带),其域名必须与 `Host` 头完全一致。
而 nginx 反代配置将 `Host` 头强制改写为 `127.0.0.1`:
```
proxy_set_header Host 127.0.0.1;
```
导致上游收到的请求为 `Host: 127.0.0.1`、`Origin: https://example.com` —— **Host 与 Origin 不同源**,被应用判定为跨站/可疑请求,返回 403。
此外,上游应用源码中的特权接口列表(`settings.*`、`credentials.*`、`host.openPath`、`llm.discoverModels` 等敏感接口)额外强制要求**仅限本机(loopback)访问**,即使配置了 `trustedHosts` 仍会被拦截,因此需一并处理。
## 四、修改方案
共三处修改,全部完成并已验证。
### 1. nginx 反代配置(已生效)
将 `Host` 头由写死的回环地址改为透传真实域名,使上游收到与 `Origin` 同源的请求:
| 修改前 | 修改后 |
| --- | --- |
| `proxy_set_header Host 127.0.0.1;` | `proxy_set_header Host $host;` |
**文件路径**:`/www/server/panel/vhost/nginx/{站点域名}.conf`
已执行 `nginx -t` 语法检查并热加载(reload)生效,不中断现有连接。
### 2. 上游应用 trustedHosts 配置(配置文件)
**文件路径**:`/root/.dsh/profiles/web/cordis.patch.yml`
为 `connection` 插件追加信任域名:
```yaml
- id: connection
config:
trustedHosts: !!js "[...ctx.webRuntime.trustedHosts, '<目标域名>']"
privilegedLoopbackOnly: false
```
- `trustedHosts`:在应用原有自动推导(LAN 地址等)基础上追加目标域名,使 Host 校验通过;
- `privilegedLoopbackOnly`:新增配置开关,详见第 3 点。
### 3. 应用源码:新增可配置开关(默认安全)
**文件路径**:`packages/client/connection/src/index.ts`(上游应用源码目录)
为插件配置新增 `privilegedLoopbackOnly` 配置项:
- **默认值 `true`**:保持应用原有安全设计,特权接口(`settings.describe`、`credentials.*`、`host.openPath`、`llm.discoverModels` 等)仅允许本机访问,通过域名访问仍返回 403;
- **设为 `false`**:特权接口允许通过 `trustedHosts` 中配置的域名访问。
该开关通过上述配置文件随时调整,**无需再修改代码**。已通过 `tsc --noEmit` 类型检查。
## 五、验证情况
| 验证项 | 结果 |
| --- | --- |
| nginx 配置语法检查 | ✅ 通过 |
| nginx 配置热加载 | ✅ 完成 |
| 应用配置解析(配置 dump) | ✅ 通过,`connection` 行组合正确 |
| 配置表达式求值逻辑 | ✅ 正确(域名已追加、原推导保留) |
| 应用源码类型检查 | ✅ 通过 |
| 上游应用服务 | 已按要求停止,待重启后生效 |
## 六、预期效果与安全说明
**重启上游应用后**:
- 普通接口(`host.describe`、`llm.providers`、`agentPreset.list` 等)恢复正常;
- 特权接口(`settings.describe` 等)在 `privilegedLoopbackOnly: false` 配置下恢复正常。
**
…________________________________
发件人: oliblue-evan ***@***.***>
发送时间: 2026年8月15日 12:20
收件人: deepseek-ai/deepseek-harness ***@***.***>
抄送: 智慧作弊机 ***@***.***>; Author ***@***.***>
主题: Re: [deepseek-ai/deepseek-harness] 通过反代访问提示大部分接口返回403 (Discussion #653)
我们遇到过完全一样的问题,已解决,分享下根因和方案:
根因
dsh 的信任围栏按 Origin.host === Host.host 精确比对(特权接口如 settings、插件管理类)。经反代后,如果 Host 头没有原样转发,围栏就会拒绝请求返回 403——普通接口不受影响,所以表现为"大部分接口 403,页面还能开"。
最常见的 Host 头丢失原因:nginx 的 proxy_set_header 作用域陷阱——proxy_set_header Host $http_host; 写在 server 级,而 location 块里有任何一条 proxy_set_header(比如 WebSocket 的 Connection/Upgrade)时,server 级的 proxy_set_header 会全部失效,Host 回落成 proxy_pass 的目标地址(如 127.0.0.1:3080),围栏比对必然失败。
修复
1. nginx:把 Host(以及 X-Forwarded-*、Upgrade/Connection)全部写在 location 块内:
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 3600s; # SSE 需要
}
1. dsh 服务:--trusted-host 必须包含反代入口的完整 authority(带端口),例如浏览器从 https://example.com:8443 访问,就要加 --trusted-host example.com:8443(Origin 里带端口,只写域名不带端口对不上)。
验证方法
改完后用 curl 模拟浏览器请求测一个特权接口:
curl -i -X POST -H "Content-Type: application/json" \
-H "Origin: https://你的域名:端口" \
-d '{"type":"client-request","rpcId":"1","method":"pluginInventory/list","payload":{"args":{}}}' \
https://你的域名:端口/api/pluginInventory/list
返回 200 即修复;仍 403 就抓一下反代转发后的实际请求头(tcpdump -i lo -A port 3080),看 Host 是否带上了正确域名和端口。
另外 @kidd-cn<https://github.com/kidd-cn> 的"配置 API key 后 DeepSeek API 请求失败"看起来是另一类问题(出站请求失败,与 403 无关),建议单独排查网络/代理出口。
—
Reply to this email directly, view it on GitHub<#653?email_source=notifications&email_token=BMHYJKEFCCR4XWO2XLCYJHL5J7QIFA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBQGI2TAOBVUZZGKYLTN5XKMYLVORUG64VFMV3GK3TUVRTG633UMVZF6Y3MNFRWW#discussioncomment-18025085>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/BMHYJKEJVWVR5H6WVG6DTZD5J7QIFAVCNFSNUABJKJSXA33TNF2G64TZHMYTGMZTGA3DKMBZGE5UI2LTMN2XG43JN5XDWMJQGYYTANJSGGQXMAQ>.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Uh oh!
There was an error while loading. Please reload this page.
All reactions