增加非白名单的日志

This commit is contained in:
andy committed 2026-09-07 12:55:43 +08:00
1 parent 7f4adad7b1
commit 06bb066d82
7 files changed
+332 -28

No files matched your search

@@ -2,8 +2,8 @@
| 项 | 内容 |
| --- | --- |
| 状态 | Implemented |
| 日期 | 2026-09-05 |
| 状态 | Implemented;已增加拒绝请求的 Origin 诊断日志 |
| 日期 | 2026-09-07 |
| 负责人 | fire-safety-ymd 后端 |
| 需求来源 | 复用既有客户端的 `/api/v1/apps/{app_id}/completion`、`xtoken` 与 `event: result` 契约 |
@@ -38,6 +38,7 @@
| `FIRE_SAFETY_CHAT_COMPAT_APP_ID` | 空 | 非空时注册兼容路径;1 至 128 个 ASCII 字母、数字、下划线或连字符 |
| `FIRE_SAFETY_CHAT_AUTH_TOKEN` | 空 | 兼容路径期望的 `xtoken`;默认至少 32 个可打印 ASCII 字符,兼容开关开启时允许已交付的旧短凭证 |
| `FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN` | `false` | 仅为已交付旧客户端短凭证的受控测试/迁移临时兼容开关;新环境不得开启 |
| `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` | 空 | 逗号分隔的精确 HTTP(S) 浏览器 Origin;不允许 `*`、非根路径、查询或片段;单个末尾 `/` 会被规范化移除 |
兼容路径只有在 `FIRE_SAFETY_CHAT_ENABLED=true` 且 App ID 非空时注册。App ID 是用于路径匹配的公开标识,不是 Secret;不匹配的路径返回 404。
@@ -120,15 +121,34 @@ SSE 开始前使用 HTTP 状态和 DashScope 风格安全 JSON:
SSE 开始后的失败发送 `event: error`,只包含稳定 code、通用 message、request ID 和本地 session ID;不发送 `finish_reason: "stop"`,也不返回任何已接收的部分回答。
沿用原生 Chat 的主要状态:400 输入错误、401 token 错误、403 Origin 错误、404 App/会话不存在、409 会话忙、503 容量不足,以及 502/504 上游失败或超时。
兼容入口的 403 响应仍只返回稳定错误 JSON;服务端日志在拒绝时提供有限诊断信息,便于确认发送方应加入哪一个精确 Origin,
但不会因为诊断而放宽鉴权或预检规则。
## 8. CORS、Nginx 与 Secret
- 浏览器 Origin 必须精确出现在 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS`;不允许 `*` 或 credentials。
- 浏览器 Origin 必须精确出现在 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS`;多个值用英文逗号分隔,不允许 `*` 或 credentials。
- 白名单值的规范形式是 `scheme://host[:port]`,不能带非根路径、查询或片段;单个末尾 `/` 会被接受并规范化移除,配置时建议省略。不要把 API URL、Nginx 上游地址或 Token 当作 Origin。
- 预检只允许 `POST` 以及 `Content-Type`、`xtoken`、`X-DashScope-SSE`、`X-Request-ID`。
- Nginx 示例只公开精确兼容路径、`/mcp` 和可选 `/health`,其余路径返回 404。
- Nginx 不比较或注入 `xtoken`、SuperAgent Open API Key、MCP Bearer 或数据库凭证;Header 原样交给 Go 验证。
- 对话 SSE 必须关闭代理缓冲、缓存、gzip 和上游自动重试,并让代理超时覆盖 Chat 总运行时限。
### 8.1 403 Origin 诊断日志
兼容入口只在拒绝场景记录诊断字段:普通 POST 的非空 Origin 不在白名单时记录
`result=forbidden_origin` 与 `origin`;CORS 预检拒绝时记录 `result=preflight_forbidden`、
`origin`、`preflight_method`、`preflight_headers` 和稳定 `reason`。`reason` 取值为
`origin_missing`、`origin_not_allowed`、`method_not_allowed` 或 `headers_not_allowed`。
每个请求 Header 最多保留前 256 个输入字节,超长值追加 `[truncated]` 后再引用/ASCII 转义,
以确保恶意换行不能伪造日志记录。成功请求不记录 Origin。日志禁止记录 `xtoken`、`Authorization`、`Cookie`、
prompt/body、会话 ID、Provider Session、Provider payload 或其他 Secret。
运维人员只能把日志中的完整 `origin` 当作排障线索,不能直接执行或无审查复制;若出现 `[truncated]`,应从浏览器
Network 面板确认完整 Origin。确认后再把 `scheme://host[:port]` 原样加入
`FIRE_SAFETY_CHAT_ALLOWED_ORIGINS`,多个来源用英文逗号分隔;禁止 `*`。修改代码需要重新 build 并 recreate,
仅修改环境配置也需要 recreate,单独 restart 不会让运行容器读取新值。
## 9. 验收标准
- Given App ID 未配置,When 请求兼容路径,Then 路由返回 404,原生 `/api/chat` 行为不变。
@@ -139,6 +159,9 @@ SSE 开始后的失败发送 `event: error`,只包含稳定 code、通用 mess
- Given 后续请求携带成功返回的 `session_id`,When 调用,Then 复用同一服务端会话映射。
- Given Provider 流失败,When SSE 已开始,Then 收到安全 `error`,不收到部分正文或 `stop`。
- Given Nginx 配置生效,When 请求未列出的路径,Then 不会转发到 Go 或外部 DashScope。
- Given 普通请求的 Origin 不在白名单,When 兼容入口返回 403,Then 日志记录有界、引用/转义后的 `origin`,但不记录 Token、Cookie、prompt/body、会话或 Provider 数据。
- Given CORS 预检因来源、方法或请求头名称集合被拒绝,When 返回 403,Then 日志记录有界、引用/转义后的 `origin`、`preflight_method`、`preflight_headers` 和稳定 `reason`,且恶意换行不能增加日志行数。
- Given Origin 白名单值被更新,When 服务重新加载配置,Then 精确 `scheme://host[:port]` 值生效,单个末尾 `/` 被规范化移除,而 `*`、非根路径、查询和片段不得被接受。
## 10. 协议来源