fix(browser): 门控 — Worker TLS verify 可配置、审计清单与 shell LF
默认校验 Worker HTTPS 证书;审计文件补全变更清单;docker-entrypoint.sh 改为 LF。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,21 +6,37 @@
|
||||
|
||||
## 范围(文件清单)
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `browser-worker/main.py` | 147 | Worker HTTP/WS API |
|
||||
| `browser-worker/session_manager.py` | 323 | Playwright + screencast |
|
||||
| `browser-worker/browser_url_safe.py` | 79 | SSRF(Worker 侧) |
|
||||
| `server/api/browser_session.py` | 198 | 桥接 REST + `/ws/browser` |
|
||||
| `server/infrastructure/browser/worker_client.py` | 83 | 连 Worker |
|
||||
| `server/infrastructure/browser/session_registry.py` | 52 | 内存会话归属 |
|
||||
| `server/utils/browser_url_safe.py` | 80 | SSRF(桥接层) |
|
||||
| `server/api/browser.py` | 88 | UI 状态 `/api/browser/state` |
|
||||
| `server/utils/browser_ui_state.py` | 165 | 状态解析 |
|
||||
| `frontend/src/composables/useRemoteBrowserSession.ts` | 213 | canvas + WS |
|
||||
| `frontend/src/components/GlobalBrowserPanel.vue` | 380 | 浮动面板 |
|
||||
| `tests/test_browser_url_safe.py` | 32 | SSRF 单测 |
|
||||
| `tests/test_browser_ui_state.py` | 35 | UI 状态单测 |
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `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 入口表
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ class Settings(BaseSettings):
|
||||
BROWSER_WORKER_SECRET: str = ""
|
||||
BROWSER_MAX_SESSIONS: int = 2
|
||||
BROWSER_IDLE_TIMEOUT_SEC: int = 1800
|
||||
BROWSER_WORKER_TLS_VERIFY: str = "true"
|
||||
|
||||
# extra=ignore: .env 中与 MCP 共存的 MYSQL_* / NEXUS_API_BASE_URL 等不进入 Settings
|
||||
model_config = SettingsConfigDict(
|
||||
|
||||
@@ -31,6 +31,10 @@ def _headers() -> dict[str, str]:
|
||||
return {"X-Nexus-Worker-Key": settings.BROWSER_WORKER_SECRET}
|
||||
|
||||
|
||||
def _tls_verify() -> bool:
|
||||
return settings.BROWSER_WORKER_TLS_VERIFY.lower() in ("1", "true", "yes")
|
||||
|
||||
|
||||
def worker_stream_url(session_id: str) -> str:
|
||||
base = settings.BROWSER_WORKER_URL.rstrip("/")
|
||||
parsed = urlparse(base)
|
||||
@@ -46,7 +50,7 @@ async def create_worker_session(session_id: str, viewport_w: int, viewport_h: in
|
||||
url = f"{settings.BROWSER_WORKER_URL.rstrip('/')}/v1/sessions"
|
||||
payload = {"session_id": session_id, "viewport_w": viewport_w, "viewport_h": viewport_h}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
|
||||
async with httpx.AsyncClient(timeout=30.0, verify=_tls_verify()) as client:
|
||||
resp = await client.post(url, json=payload, headers=_headers())
|
||||
except httpx.RequestError as exc:
|
||||
logger.warning("worker create failed: %s", exc)
|
||||
@@ -62,7 +66,7 @@ async def delete_worker_session(session_id: str) -> None:
|
||||
return
|
||||
url = f"{settings.BROWSER_WORKER_URL.rstrip('/')}/v1/sessions/{session_id}"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=15.0, verify=False) as client:
|
||||
async with httpx.AsyncClient(timeout=15.0, verify=_tls_verify()) as client:
|
||||
await client.delete(url, headers=_headers())
|
||||
except httpx.RequestError as exc:
|
||||
logger.warning("worker delete failed session=%s: %s", session_id, exc)
|
||||
@@ -73,7 +77,7 @@ async def worker_health() -> dict[str, Any] | None:
|
||||
return None
|
||||
url = f"{settings.BROWSER_WORKER_URL.rstrip('/')}/health"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0, verify=False) as client:
|
||||
async with httpx.AsyncClient(timeout=5.0, verify=_tls_verify()) as client:
|
||||
resp = await client.get(url)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
|
||||
Reference in New Issue
Block a user