dbb6bc4a55
默认校验 Worker HTTPS 证书;审计文件补全变更清单;docker-entrypoint.sh 改为 LF。 Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
7.2 KiB
Markdown
143 lines
7.2 KiB
Markdown
# 审计 — 远程浏览器(Playwright Worker)
|
||
|
||
**Changelog**: `docs/changelog/2026-06-12-remote-browser-worker-deploy.md`
|
||
**设计 SSOT**: `docs/design/specs/2026-06-11-server-browser-chromium-design.md`
|
||
**范围**: 2026-06-12 新增/恢复的浏览器 Worker 全链路(未含 `web/app` 构建产物)
|
||
|
||
## 范围(文件清单)
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `browser-worker/Dockerfile` | Worker 镜像 |
|
||
| `browser-worker/docker-compose.yml` | Worker 编排 |
|
||
| `browser-worker/docker-entrypoint.sh` | headed + Xvfb 入口 |
|
||
| `browser-worker/requirements.txt` | Worker 依赖 |
|
||
| `browser-worker/config.py` | Worker 环境配置 |
|
||
| `browser-worker/main.py` | Worker HTTP/WS API |
|
||
| `browser-worker/session_manager.py` | Playwright + screencast |
|
||
| `browser-worker/browser_stealth.py` | 反自动化 / 验证码辅助 |
|
||
| `browser-worker/browser_url_safe.py` | SSRF(Worker 侧) |
|
||
| `server/main.py` | 注册 browser 路由 |
|
||
| `server/config.py` | `BROWSER_*` 与 TLS verify |
|
||
| `server/api/settings.py` | 设置项默认值(push_complete_sound) |
|
||
| `server/api/browser_session.py` | 桥接 REST + `/ws/browser` |
|
||
| `server/api/browser.py` | UI 状态 `/api/browser/state` |
|
||
| `server/infrastructure/browser/__init__.py` | 包入口 |
|
||
| `server/infrastructure/browser/worker_client.py` | 连 Worker(可配置 TLS verify) |
|
||
| `server/infrastructure/browser/session_registry.py` | 内存会话归属 |
|
||
| `server/utils/browser_url_safe.py` | SSRF(桥接层) |
|
||
| `server/utils/browser_ui_state.py` | 状态解析 |
|
||
| `frontend/src/App.vue` | 顶栏 + 固定 GlobalBrowserPanel |
|
||
| `frontend/src/router/index.ts` | `/browser` 路由 |
|
||
| `frontend/src/pages/ServersPage.vue` | 服务器页打开浏览器 |
|
||
| `frontend/src/pages/BrowserPage.vue` | 独立浏览器页(兼容) |
|
||
| `frontend/src/components/GlobalBrowserPanel.vue` | 固定顶栏下整窗 |
|
||
| `frontend/src/composables/useGlobalBrowser.ts` | 全局标签/状态 |
|
||
| `frontend/src/composables/useRemoteBrowserSession.ts` | canvas + WS |
|
||
| `tests/test_browser_url_safe.py` | SSRF 单测 |
|
||
| `tests/test_browser_ui_state.py` | UI 状态单测 |
|
||
| `tests/test_settings_sound_defaults.py` | 设置默认音单测 |
|
||
|
||
## API / WS 入口表
|
||
|
||
| 方法 | 路径 | 鉴权 |
|
||
|------|------|------|
|
||
| GET | `/api/browser/status` | JWT `get_current_admin` |
|
||
| POST | `/api/browser/sessions` | JWT |
|
||
| DELETE | `/api/browser/sessions/{id}` | JWT + 会话归属 |
|
||
| GET/PUT | `/api/browser/state` | JWT + `admin_ui_preferences` |
|
||
| WS | `/ws/browser/{session_id}?token=` | JWT + 会话归属 |
|
||
| GET | Worker `/health` | **无** |
|
||
| POST/DELETE | Worker `/v1/sessions*` | `X-Nexus-Worker-Key` |
|
||
| WS | Worker `/v1/sessions/{id}/stream` | Worker Key(Header) |
|
||
|
||
## Step 3 规则扫描
|
||
|
||
| ID | 规则 | 结论 |
|
||
|----|------|------|
|
||
| H1 | SSRF / 私网访问 | **RISK** — 见 F1、F2 |
|
||
| H2 | 鉴权 / IDOR | PASS — WS 校验 admin_id |
|
||
| H3 | 密钥不落库/不入 git | PASS — `.env` / 持久卷 |
|
||
| H4 | 无静默吞错 | **RISK** — 见 F4 |
|
||
| H5 | Worker 暴露面 | **RISK** — 见 F3 |
|
||
| H6 | 多 worker 一致性 | **RISK** — 见 F5 |
|
||
| H7 | UI 状态 URL 白名单 | PASS — `browser_ui_state` |
|
||
| H8 | 设计符合度(redirect) | **GAP** — 见 F1 |
|
||
|
||
## Closure(全表)
|
||
|
||
| ID | 判定 | 严重度 | 位置 | 说明 |
|
||
|----|------|--------|------|------|
|
||
| H1 | FINDING | **P1** | `browser-worker/session_manager.py` NAVIGATE 后 | **F1** 页内点击/重定向未逐跳 SSRF 校验;设计文档要求 redirect 后重验 |
|
||
| H1 | FINDING | **P1** | Worker 部署 ufw | **F3** 装机时额外 `ufw allow 8443/tcp`(全网),削弱「仅主站 IP」隔离 |
|
||
| H2 | SAFE | — | `browser_session.py:125-127` | `session_id` 必须属于当前 `admin.id` |
|
||
| H2 | SAFE | — | `browser_session.py:95-104` | 桥接层 NAVIGATE 二次校验 |
|
||
| H3 | SAFE | — | `worker_client.py`, Worker `.env` | `WORKER_SECRET` 仅环境变量 |
|
||
| H4 | FINDING | P2 | `worker_client.py:67-68` | **F4** `delete_worker_session` 网络失败仅 warning,可能残留 Worker 会话 |
|
||
| H5 | FINDING | P2 | `browser-worker/main.py:45-51` | `/health` 无认证;若 8443 对公网可见会泄露会话数 |
|
||
| H6 | FINDING | P2 | `session_registry.py` | **F5** 内存注册表不跨 uvicorn worker;多进程下 WS 可能 4403 |
|
||
| H7 | SAFE | — | `browser_ui_state.py` | http(s) only,无凭据 URL |
|
||
| H8 | SAFE | — | screencast 热修 | `cdp.on("Page.screencastFrame")` 已替代错误 API |
|
||
| — | SAFE | — | `browser_session.py:61-64` | 创建前清理陈旧会话,避免 409 |
|
||
| — | SAFE | — | 前端 | 不再 iframe;canvas 远程渲染 |
|
||
| — | SAFE | — | CUD 审计 | 首版未记 session 审计,与设计「与 RDP 一致」一致 |
|
||
|
||
### F1 — 重定向 / 页内导航 SSRF 缺口(P1)
|
||
|
||
`validate_navigation_url` 仅在显式 `NAVIGATE` 消息时调用。用户在远程 Chromium 内**点击链接**或 **302 跳转**到 `http://10.x` / metadata 时,Playwright 会照常请求,**不经过**校验。
|
||
|
||
**建议**:`page.on("framenavigated")` / `response` 监听,每跳对 `page.url` 调用 `validate_navigation_url`;失败则 `page.close()` 或 `about:blank` 并 WS `ERROR`。
|
||
|
||
### F3 — Worker 防火墙过宽(P1)
|
||
|
||
部署日志显示除 `from 20.24.218.235` 外还有 `ufw allow 8443/tcp`(Anywhere)。攻击者若猜到 `WORKER_SECRET`(或暴力)可直连 Worker。
|
||
|
||
**建议**:删除全网 8443 规则,仅保留主站源 IP;`/health` 改内网或加 Key。
|
||
|
||
### F4 — Worker 删除失败(P2)
|
||
|
||
`delete_worker_session` 异常时静默;依赖 Worker 空闲超时清理。
|
||
|
||
### F5 — 多 uvicorn worker(P2)
|
||
|
||
`session_registry` 进程内字典;生产若 `--workers>1` 可能 POST 在 worker A、WS 落到 worker B。当前 Docker 单进程可接受;扩 worker 时需 Redis 注册表。
|
||
|
||
## 外部输入 → Sink
|
||
|
||
| 输入 | Sink | 缓解 |
|
||
|------|------|------|
|
||
| `NAVIGATE.url` | Playwright `goto` | 双端 `validate_navigation_url` |
|
||
| 页内链接点击 | Playwright 导航 | **未缓解(F1)** |
|
||
| WS `MOUSE`/`KEY` | Chromium | 仅已认证管理员 |
|
||
| `browser/state` tabs URL | MySQL JSON | `_is_safe_url` 过滤 |
|
||
| Worker Key | Worker API | 常量时间比较(`!=`) |
|
||
|
||
## DoD
|
||
|
||
| 项 | 状态 |
|
||
|----|------|
|
||
| `pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py` | 7 passed(生产机);本地沙箱 DNS 失败属环境限制 |
|
||
| `npm run type-check` | 已通过(实现日) |
|
||
| 生产 `GET /api/browser/status` | `enabled:true`, `worker_ok:true` |
|
||
| 生产 screencast 冒烟 | `SCREENCAST_OK` ~4KB JPEG |
|
||
| changelog | `docs/changelog/2026-06-12-remote-browser-worker-deploy.md` |
|
||
|
||
## 总结
|
||
|
||
| 级别 | 数量 | 处置建议 |
|
||
|------|------|----------|
|
||
| P0 | 0 | — |
|
||
| P1 | 2 | F1 redirect/页内 SSRF;F3 收紧 ufw |
|
||
| P2 | 3 | F4/F5 可排期;/health 加固 |
|
||
|
||
**合并结论**:功能链路可用,鉴权与显式 NAVIGATE SSRF 达标;**未达设计文档「每跳 redirect 校验」**,且 Worker **8443 应对公网收口**。建议先修 F3(运维),再实现 F1(代码)后复审计。
|
||
|
||
## 验证命令
|
||
|
||
```bash
|
||
venv/bin/pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py -q
|
||
cd frontend && npm run type-check
|
||
# 生产
|
||
curl -H "Authorization: Bearer $TOKEN" https://api.synaglobal.vip/api/browser/status
|
||
```
|