增加非白名单的日志

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

@@ -143,6 +143,9 @@ FIRE_SAFETY_POSTGIS_EXPECTED_SRID=4326
- FIRE_SAFETY_CHAT_PAGE_ENABLED 默认必须为 false。开启后 Go 提供 `/chat`、`/chat/` 以及同源的
`/chat/app.css`、`/chat/app.js`;`/chat` 会 308 到 `/chat/`,页面和资源的最终可用性仍由 Go
开关决定。页面只用于受控测试,不嵌入或持久化 Token。
- `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 是 Go 侧的精确 Origin 白名单,多个来源用英文逗号分隔;每项的规范形式是
`scheme://host[:port]`,不能带非根路径、查询或片段,也禁止 `*`。单个末尾 `/` 会被接受并规范化移除,配置时建议省略。复杂网络环境不要猜测来源,先从
兼容 Chat 的 403 诊断日志或对方浏览器 Network 面板确认完整 Origin,再修改该值。
- FIRE_SAFETY_SUPERAGENT_INCLUDE_TRACE 默认必须为 false。无 Trace 的严格成功条件是最终 AI 消息
`finish_reason=stop`、非空顶层 `event: message.final` 加顶层 `event: end`;设置为 true 时还要求
`run.completed(status=success)`,且外部应用策略必须开启 `trace_policy.enabled=true`。无 Trace
@@ -306,6 +309,60 @@ curl -N --fail \
至少验证:错误 xtoken 为 401;不存在的 app ID 和未列出的路径为 404。不要使用真实联系人、电话或精确敏感位置作为冒烟问题。
### 7.1 403 Origin 诊断与白名单调整
如果兼容 completion 返回 HTTP 403,先在响应中记录 `request_id`,再在服务器查看有限的 Go 日志:
~~~bash
cd /home/firee-safety-ymd
docker compose logs --no-color --since=10m --tail=200 api \
| grep -E 'dashscope_chat_request.*result=(forbidden_origin|preflight_forbidden)'
~~~
日志含义如下:
- `result=forbidden_origin`:普通 POST 的非空 `Origin` 不在白名单;日志中的 `origin` 最多保留前 256
个输入字节,超长值追加 `[truncated]` 后再引用/ASCII 转义。
- `result=preflight_forbidden`:CORS 预检被拒绝;除 `origin` 外还会记录有界的
`preflight_method`、`preflight_headers` 和 `reason`。原因只可能是 `origin_missing`、
`origin_not_allowed`、`method_not_allowed` 或 `headers_not_allowed`。
- 出现 `[truncated]` 时日志不足以配置白名单,必须从对方浏览器 Network 面板确认完整 Origin;不要执行日志中的
未可信内容或把它当作 shell 变量展开。
确认发送方后,才把精确来源写入服务器 `.env`,例如:
~~~text
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com,http://ui.example:9045
~~~
示例中的第二个值只能在 Network 面板确认为 `http://ui.example:9045` 时使用。不要加入 API 路径、
Nginx 上游地址、Token 或 `*`。这些拒绝日志不记录 `xtoken`、`Authorization`、`Cookie`、prompt/body、
会话或 Provider 数据。
若 Go 日志没有对应的 `dashscope_chat_request`,再检查 Nginx 是否在到达 Go 前拒绝或关闭连接;只读取有限日志,
不要使用会把旧配置 Secret 全部打印出来的命令:
~~~bash
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 200 /var/log/nginx/access.log \
| grep -E ' /api/v1/apps/[^ ]+/completion '
~~~
修改 `.env` 后必须重新创建容器,单独 `restart` 不会读取新值:
~~~bash
cd /home/firee-safety-ymd
docker compose config --quiet
docker compose up -d --force-recreate --no-build api
~~~
如果同时更新了 Go 日志代码,必须重新构建并创建容器:
~~~bash
docker compose build --pull
docker compose up -d --force-recreate --remove-orphans
~~~
完成 Chat 测试后清理当前 shell 中的临时变量:
~~~bash
@@ -576,6 +633,8 @@ docker compose down
- 目标机 nginx -t 和 reload 成功;443 证书、DNS、安全组及旧 location / 已确认不再生效。
- 页面开关关闭时 `/chat`、`/chat/`、`/chat/app.css`、`/chat/app.js` 直接返回 404;开启时 `/chat` 返回 308、`/chat/` 返回 200 HTML,两个资源返回 200;`FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 包含 `https://agent.nianxx.com`。
- Chat 首轮/多轮 SSE、错误凭证、未知 app/path 的实际 HTTPS 响应。
- 兼容 Chat 的 403 Origin 排障:拒绝日志只在拒绝场景记录有界、引用/转义后的 Origin;预检拒绝还记录有界
方法、请求头名称集合和稳定原因;日志中不得出现 Token、Cookie、prompt/body、会话或 Provider 数据。
- 页面浏览器验收证明用户手动输入 xtoken、Token 不被页面持久化、兼容 completion SSE 及同页面 `session_id` 复用;这只是静态测试页面,不是生产认证。
- 若为已交付旧客户端临时开启 Chat legacy 短凭证兼容,应记录受控迁移窗口,确认启动 warning 不含 Secret,并在凭证轮换后恢复 `FIRE_SAFETY_CHAT_ALLOW_LEGACY_SHORT_TOKEN=false`。
- SuperAgent Open API 使用 `FIRE_SAFETY_SUPERAGENT_INCLUDE_TRACE=false` 时,验证最终 AI 消息
+49 -4
View File
@@ -53,7 +53,8 @@ Nginx 不再调用 DashScope,也不保存或注入 SuperAgent Open API Key、C
页面默认关闭。测试开启时,Go 进程配置的 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 必须包含精确
Origin `https://agent.nianxx.com`;该值是浏览器从页面同源发出 completion 请求时的来源。不要
用 `*`,也不要把 Token 写入 Nginx。
用 `*`,也不要把 Token 写入 Nginx。Nginx 必须保留客户端的 `Origin` Header 原样交给 Go,不要在
代理层删除、改写或用固定值覆盖它;白名单判断和 403 诊断日志均由 Go 完成。
`limit_req_zone` 必须位于 Nginx `http` context,不能放进 `server` 或 `location`。示例文件假定它被 `conf.d/*.conf` 从 `http {}` 中 include;如果部署系统不是这样 include,应把两条 `limit_req_zone` 指令单独移到 `http {}`,并保留 `server`/`upstream` 在合法上下文。
@@ -118,7 +119,51 @@ FIRE_SAFETY_POSTGIS_DSN=<readonly-postgresql-dsn>
MCP 是普通 JSON 请求,不需要 SSE 的关闭响应缓冲设置;示例使用 30 秒读写超时,仍关闭上游自动重试。
## 5. 启用与检查
## 5. 兼容 Chat 403 Origin 诊断
兼容 completion 返回 HTTP 403 时,先用响应中的 `request_id` 与 Go 日志关联。Go 只在拒绝场景记录
受限诊断字段:
- 普通请求的非空 Origin 不在白名单时,记录 `result=forbidden_origin`;`origin` 最多保留前 256 个
输入字节,超长值追加 `[truncated]` 后再引用/ASCII 转义;
- CORS 预检拒绝时,记录 `result=preflight_forbidden`、同样有界的 `origin`、`preflight_method`、
`preflight_headers` 和 `reason`;
- `reason` 只表示 `origin_missing`、`origin_not_allowed`、`method_not_allowed` 或
`headers_not_allowed`;成功请求不记录 Origin。
这些日志值是非可信请求输入,经过引用/转义且有长度边界;日志不记录 `xtoken`、`Authorization`、
`Cookie`、prompt/body、会话或 Provider 数据。仅在受限运维终端读取有限日志,不要把完整日志粘贴到公开工单:
```bash
cd /home/firee-safety-ymd
docker compose logs --no-color --since=10m --tail=200 api \
| grep -E 'dashscope_chat_request.*result=(forbidden_origin|preflight_forbidden)'
```
如果日志值带 `[truncated]`,不能据此配置白名单;回到对方浏览器的 Network 面板读取完整 `Origin`。
确认来源后,使用规范形式 `scheme://host[:port]`,不能带非根路径、查询或片段,多个来源用英文逗号分隔,
禁止 `*`。单个末尾 `/` 会被接受并规范化移除,配置时建议省略。例如仅当 Network 面板确认来源确为
`http://ui.example:9045` 时,才配置:
```text
FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com,http://ui.example:9045
```
修改 `.env` 后必须重新创建容器;只改配置时可不重建镜像:
```bash
cd /home/firee-safety-ymd
docker compose config --quiet
docker compose up -d --force-recreate --no-build api
```
若同时更新了 Go 代码,必须先拉取明确 revision,再执行 `docker compose build --pull` 和
`docker compose up -d --force-recreate --remove-orphans`。修改 Nginx 配置本身时,仍须先执行
`sudo nginx -t`,通过后再 reload。Nginx 层若返回 HTML 403 或连接被关闭,而 Go 没有对应
`dashscope_chat_request` 日志,应检查实际生效的 server/location、旧配置和 Nginx error log,不能通过
删除 `Origin` 或恢复旧的全路径 DashScope 代理绕过问题。
## 6. 启用与检查
启动 Go 服务前,在服务器的进程环境中加载 Secret。不要把 Secret 直接写进命令行或 shell 历史;可以在受保护的环境文件中加载后,再通过 Header 环境变量展开:
@@ -228,7 +273,7 @@ curl --fail \
预期 `initialize` 返回协议版本 `2025-06-18`,initialized notification 返回 HTTP 202,`tools/list` 返回 7 个固定工具。只收到 HTTP 200 但 JSON-RPC 中含 `error` 也属于失败,不能把它当作 MCP 已就绪。
## 6. 验收边界与回滚
## 7. 验收边界与回滚
完成 `nginx -t` 和 reload 后,应至少验证:
@@ -246,7 +291,7 @@ curl --fail \
路径均直接返回 404。这样不会自动关闭兼容 completion;如果也要关闭对话 API,再按 Chat 配置和对应回滚流程
处理。页面开关和 Nginx 路由均应保留清晰的变更记录。
## 7. 未确认事项
## 8. 未确认事项
- 目标公网机器的 Nginx 版本、include 层级、TLS 终止位置和证书续期方式;示例同时监听 80 做 HTTPS 跳转,安全组需按实际策略决定是否允许 80。
- `agent.nianxx.com` 的 DNS、安全组、反向代理来源 IP 和 SuperAgent 对 MCP 回调的网络策略。
@@ -3,8 +3,8 @@
| 项 | 内容 |
| --- | --- |
| 项目 | `fire-safety-ymd` |
| 状态 | SuperAgent 出站、用户对话 API v1 与空间只读 MCP 代码控制已实现;真实环境、最终用户授权和持久审计待完成 |
| 最近更新 | 2026-09-05 |
| 状态 | SuperAgent 出站、用户对话 API v1、兼容 Chat 403 Origin 诊断日志与空间只读 MCP 代码控制已实现;真实环境、最终用户授权和持久审计待完成 |
| 最近更新 | 2026-09-07 |
## 1. 目的
@@ -63,7 +63,12 @@ MCP 第一阶段已按以下只读边界实现:
- 权限不足与“没有数据”使用不同稳定状态,避免 Agent 猜测。
- 调用记录至少关联 request ID、可信主体、工具名、授权范围、耗时、结果类别和数据源版本;审计日志不保存无必要的完整敏感正文。
当前日志已记录 request ID、操作、耗时和结果类别;可信主体、数据源版本和持久审计尚未实现,因此生产前仍需补齐。
当前日志已记录 request ID、操作、耗时和结果类别;可信主体、数据源版本和持久审计尚未实现,因此生产前仍需补齐。兼容
`completion` 的拒绝路径另有受限诊断字段:非预检请求仅在 Origin 被拒绝时记录
`result=forbidden_origin` 和实际 Origin;CORS 预检拒绝记录
`result=preflight_forbidden`、实际 Origin、请求方法、请求头名称集合和稳定 `reason`。
每个请求 Header 最多保留前 256 个输入字节,超长值追加 `[truncated]` 后再引用/ASCII 转义;成功请求不记录
Origin。日志绝不记录 `xtoken`、`Authorization`、`Cookie`、prompt/body、会话 ID 或 Provider 数据。
任何写工具、资源调度或状态变更都需要新的 Spec、幂等设计、人工确认边界、审计和安全 Review,不属于默认扩展。
@@ -109,10 +114,31 @@ MCP 第一阶段已按以下只读边界实现:
| 精确设施或风险区域坐标 | 业务敏感 | 按权限最少披露,不记录完整结果集 |
| 用户问题和对话 | 可能含敏感信息 | 默认不记录原文,使用摘要或分类字段 |
| 工具名、耗时、结果类别 | 运行元数据 | 可记录,不附敏感 payload |
| 兼容 Chat 被拒绝的 Origin 诊断字段 | 受限请求元数据 | 仅拒绝时记录引用/转义且有界的 Origin;预检附方法、请求头名称集合和原因;禁止 Secret 与请求正文 |
生产前需确定数据分类负责人、日志访问角色、保留周期、删除流程和安全事件响应方式。
当前 Chat 日志只记录 request ID、结果类别、是否复用和耗时,不记录消息、回答、对话 ID、Provider Session 或 Trace payload。`docs/import/db-samples/*.sql` 含真实联系人、电话和精确坐标,仅作为本地只读输入并由 Git 忽略;不得执行或进入版本历史。长期测试数据必须另做脱敏 fixture。
当前 Chat 日志只记录 request ID、结果类别、是否复用和耗时,不记录消息、回答、对话 ID、Provider Session 或 Trace payload。
兼容 `completion` 的 403 Origin 诊断是例外但仍受严格边界约束:`dashscope_chat_request` 在
`result=forbidden_origin` 时记录有界的 `origin`;每个请求 Header 最多保留前 256 个输入字节,超长值追加
`[truncated]` 后再引用/ASCII 转义。在
`result=preflight_forbidden` 时再记录同样有界的 `preflight_method`、`preflight_headers` 和稳定
`reason`(`origin_missing`、`origin_not_allowed`、`method_not_allowed` 或 `headers_not_allowed`)。
这些字段只用于定位发送方的精确浏览器 Origin,不是授权凭证;不记录 `xtoken`、`Authorization`、`Cookie`、
prompt/body、会话或 Provider 数据。`docs/import/db-samples/*.sql` 含真实联系人、电话和精确坐标,仅作为本地只读输入并由 Git 忽略;不得执行或进入版本历史。长期测试数据必须另做脱敏 fixture。
### 8.1 兼容 Chat 403 诊断与 Origin 白名单
本节只适用于 DashScope 风格兼容 `completion`。收到 HTTP 403 后,运维人员在受限日志中按 `request_id`
关联响应;`result=forbidden_origin` 表示普通请求的 Origin 不在白名单,`result=preflight_forbidden`
还需根据 `reason` 判断是来源、方法还是请求头名称集合不符合预检契约。日志中的值是经过引用/转义的非可信输入,
不能当作 shell 代码执行;出现 `[truncated]` 时必须回到浏览器 Network 面板确认完整值。
确认发送方后,将精确的浏览器来源配置到 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS`:多个值用英文逗号分隔,
每项的规范形式是 `scheme://host[:port]`,不能带非根路径、查询或片段,也禁止 `*`;单个末尾 `/` 会被接受并
规范化移除,配置时建议省略。例如页面来源确实为 `http://example.test:9045` 时才加入该值;不要把 API
路径、Nginx 上游地址或 `xtoken` 放入白名单。
修改环境配置后必须重新 build(代码变更时)并 recreate 运行容器,不能依赖 `restart` 重新加载配置。
## 9. 应急场景安全
@@ -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. 协议来源