默认校验 Worker HTTPS 证书;审计文件补全变更清单;docker-entrypoint.sh 改为 LF。 Co-authored-by: Cursor <cursoragent@cursor.com>
7.2 KiB
审计 — 远程浏览器(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(代码)后复审计。
验证命令
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