feat: v0.12.2 —— 跨域只读支持(面板可部署在别处)

用户诉求:管理端与前端完全分离,本地探针只跑 /admin,前端部署到 GitHub Pages
并硬编码管理地址,探测仍在本地常驻。用户自己搭隧道给本地套 HTTPS。

硬限制(务必先看):GitHub Pages 强制 HTTPS,而浏览器「混合内容」策略禁止
HTTPS 页面 fetch HTTP 资源 —— 这与 CORS 无关,改设置也没用。
因此 settings.dataUrl 必须与面板页面同为 HTTPS。

服务端
- 只读接口(/api/status、/api/monitors、/api/events、/api/v2/getMonitors、
  /api/refresh)按 settings.corsOrigins 白名单回显 Access-Control-Allow-Origin
- /api/admin/* 一律不放行跨域,预检直接 403 —— 外部页面既读不到也改不了
- 绝不使用 '*':白名单里写 '*' 不放行任何来源

前端
- settings.dataUrl 存在时,所有 api/ 相对路径自动改写到该地址
- 数据源标记为「远程探针(跨域)」,页脚可区分
- 管理界面新增两项配置:跨域数据源地址、允许跨域的页面地址(改完立即生效)

关键设计:origin 匹配必须考虑端口
origin 是「协议 + 主机 + 端口」的完整组合。最初白名单存 http://localhost(无端口),
导致本地前端跑在 localhost:5173 时被拒 —— 浏览器实测 Failed to fetch 而 curl 能过。
修法:回环主机(localhost / 127.0.0.1)忽略端口放行,其余要求完整 origin 精确匹配。

测试:verify-security 25→40 项,新增 [D3] CORS 白名单 14 条 + [D4] '*' 写法 1 条。
全项目 8 套 334 项通过。

附记一个测试上的教训:跨域 fetch 时 JS 读不到 Access-Control-Allow-Origin
(CORS 规范规定只有同源能读),所以断言「是否放行」要看 fetch 有没有拿到数据,
不能看响应头 —— 我一开始用 headers.get() 判断,拿到 null 误以为失败。
This commit is contained in:
tamakyi
2026-10-14 16:19:31 +08:00
parent f345626840
commit 6b0a23c38e
16 changed files with 835 additions and 575 deletions
+22 -1
View File
@@ -522,6 +522,27 @@ node prober/node/index.js
> ⚠️ 注意:打开后同一端口上的公开面板(`/` 与 `/api/status`)也会跟着对外可达。它们本身是只读的,但目标名称、地址与可用率会一并暴露。如果不希望这样,请在你自己那层(反向代理/隧道)按路径收窄。
### 面板部署在别处(跨域只读)
面板与探针可以放在两台机器上:面板放 GitHub Pages / Cloudflare(静态托管),探针放你家里或内网的机器上持续探测。两项配置:
| 配置 | 作用 |
| --- | --- |
| `settings.dataUrl` | 面板去哪里取数据,例 `https://monitor.example.com`。留空表示与探针同源 |
| `settings.corsOrigins` | 允许跨域读取的**页面地址**,逗号分隔的完整 origin,如 `https://you.github.io` |
两者都在管理界面里填(改完立即生效)。
**硬限制(务必先看)**:GitHub Pages 强制 HTTPS,而浏览器的**混合内容**策略禁止 HTTPS 页面 `fetch` HTTP 资源 —— 这与 CORS 无关,改设置也没用。**因此 `dataUrl` 必须与面板页面同为 HTTPS**(你自己搭隧道给本地探针套 HTTPS 即可)。
安全设计:
- **绝不使用 `Access-Control-Allow-Origin: *`** —— 那等于让任意网站都能读走你的监控数据。默认只放行本机回环地址(`localhost` / `127.0.0.1`,**忽略端口**,因为本地前端常跑在 5173 这类端口)
- 白名单里的 origin 必须**精确匹配**,`https://you.github.io.evil.com` 这种前缀伪造会被拒
- 回显具体 origin 并带 `Vary: Origin`,避免中间缓存串味
- **管理端永不放行跨域**:`/admin` 与 `/api/admin/*` 不带任何 `Access-Control-*` 头,外部页面无法读取也无法写入
- 跨域读到的配置同样经过密钥剥离
### 部署形态与可写能力
| 部署形态 | 面板可读 | 探测 | 通知/维护窗口/事件 | 管理端(2FA) |
@@ -814,7 +835,7 @@ NODE_PATH=<playwright 的 node_modules> node scripts/verify-views.cjs # 视
NODE_PATH=<playwright 的 node_modules> node scripts/verify-layout.cjs # 13 视口 × 2 视图零横向溢出 + 行高一致(26 项)
NODE_PATH=<playwright 的 node_modules> node scripts/verify-admin.cjs # 管理端 2FA 全流程(102 项:RFC 向量 / 绑定 / 会话 / 限流 / 接口 / 页面)
node scripts/verify-worker.cjs # Cloudflare Worker(40 项:UR 映射 / 探测范围 / 失败降级 / 管理端拒绝 / 密钥泄露)
node scripts/verify-security.cjs # banner 探测 / 脱敏 / 私有分层 / 刷新限流 / 配置接口密钥泄露(25 项)
node scripts/verify-security.cjs # banner 探测 / 脱敏 / 私有分层 / 刷新限流 / 密钥泄露 / CORS 白名单(40 项)
node scripts/verify-notify.cjs # 通知与证书(44 项:假 SMTP / 假 MeoW / 去抖 / 恢复 / 维护窗口 / 真实证书)
UPTIMEROBOT_API_KEY=ur... node scripts/verify-uptimerobot.cjs # UR 映射/兼容接口/失败路径/冒烟(18 项)
```