browser: click-path allowlist, safe tenant script upgrade, allowlist activation plan

- click/click_ref: pre-click allowlist gate on resolvable anchor href
  (same check as navigate; miss refuses before the click executes);
  click/click_ref/click_at: post-click landing URL check.
  click_at has no pre-click gate by design (coordinates name no
  element); JS-driven navigation, evaluate, meta refresh and popups
  are documented as unintercepable with the Tinyproxy 403 backstop.
- Tenant script upgrade is now safe: only proven-unmodified scripts
  (manifest match, 2a569ac known-shipped SHA-256, missing, or
  current) auto-merge; tenant-modified scripts are never overwritten,
  are reported as skipped, and get one deduped audit record. The
  manifest never learns a tenant hash (else the next upgrade would
  mistake a modified file for untouched). Idempotent reruns.
- Activation candidate = deployed Tinyproxy programming set encoded
  with leading-dot entries (plain entries would regress
  docs.python.org); npmjs.org per the deployed filter, not npmjs.com.
  Host activation itself is NOT performed this round: only the plan,
  validation tests and rollback are delivered.
Tests: 28 new (click 13, upgrade 8, activation 7), all fake/no-network;
98 browser-related tests pass including prior suites.

(cherry picked from commit e4a93f5748ec815f163d0bc096ff4dc4b8c651f5)
This commit is contained in:
host committed 2026-11-01 09:06:28 +00:00
1 parent 1141d0d0f8
commit 125b436ab3
8 files changed
+1150 -33

No files matched your search

+10 -3
View File
@@ -2765,9 +2765,10 @@ class Worker:
# 与 imagegen 同款:播种失败直接抛错、任务不派发
# ——静默无浏览器比 loud 失败更糟,尤其对已宣传
# “能开浏览器”的任务。代理与浏览器路径由
# _prepare_tenant 的 env 块给。MCP 脚本是系统文件:
# 播种器在版本变化时刷新老租户的旧脚本(见
# ensure_codex_home_playwright),刷新记录打日志。
# _prepare_tenant 的 env 块给。播种器只对已证明
# 未改动的租户脚本自动合并新版(见
# ensure_codex_home_playwright);租户改过的脚本
# 绝不覆盖,只记审计并打 warning 留待人工处理。
_seeded = _ensure_playwright_mcp(
dirs["codex_home"],
command_args=[
@@ -2779,6 +2780,12 @@ class Worker:
"playwright MCP scripts refreshed for project %s: %s",
task.get("project_id"),
",".join(_seeded["script_refreshed"]))
if _seeded.get("script_skipped_modified"):
self.logger.warning(
"playwright MCP scripts skipped (tenant-modified, "
"left untouched, see audit) for project %s: %s",
task.get("project_id"),
",".join(_seeded["script_skipped_modified"]))
if self.seed_skills:
# 技能播种失败绝不阻塞任务:技能是可选的流程
# 说明,不是 websearch 那种“已宣传就必须有”的