Skip to content

feat(browser): add sandboxed local HTML preview - #1803

Open
happy9zhang wants to merge 52 commits into
makecindy:mainfrom
happy9zhang:codex/preview-local-html
Open

feat(browser): add sandboxed local HTML preview#1803
happy9zhang wants to merge 52 commits into
makecindy:mainfrom
happy9zhang:codex/preview-local-html

Conversation

@happy9zhang

@happy9zhang happy9zhang commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

agent 需要"截图验证本地生成的 HTML"时,此前会因 file:// 与严格 SSRF 策略被拒而逃逸到裸跑 Chrome(历史上因此发生 taskkill /F /IM chrome.exe 误杀用户浏览器的事故,见 #1766)。本 PR 提供受控的本地 HTML 预览通道:新增 previewLocalHtml action,通过 token 化的 loopback 预览服务在受管浏览器中打开工作区内 HTML,agent 无需再逃逸。

怎么验证的

自动验证

pnpm --filter @cindy/browser-control-runtime build    # tsc --noEmit 通过
pnpm --filter @cindy/browser-control-runtime test     # 49 passed(导航级 allowedOrigins + 持续守卫测试)
pnpm --filter @cindy/mcps build                       # tsc --noEmit 通过
pnpm --filter desktop typecheck                       # tsc --noEmit 通过
desktop vitest: mcp-integrations 全套                  # 568 passed(round 23)
desktop vitest: updateService.test.ts                 # 21 passed(round 23 架构修复后)
desktop vitest: e2e-integration tier(真实 Chrome)    # 1 passed(专用 integration tier)
lizi-mcps build                                       # tsc --noEmit 通过

(lizi-mcps 的 contactsTools 15 个失败为环境性失败——stash 全部改动后仍失败,与本次改动无关)

桌面端到端验证(2A,真实环境)

3 种 Agent 底座 × 2 种浏览器后端 = 6 个组合全部通过(Claude/Codex/Pi × 外置 Chrome/侧边栏 RSB):每轮预览、snapshot、截图;多轮截图复核渲染一致(CSS/JS/相对资源均生效)。越界(../)、隐藏目录、无工作区会话三种攻击路径在三底座下全部被拒。用户日常 Chrome 全程未被影响。

评审迭代

已回应 22 轮自动评审(Greptile / Copilot / chatgpt-codex-connector / 本地独立 Codex 审查者),全部意见已修复并附测试:

  1. 隐藏目录全路径段拒绝(非仅 basename)
  2. CSP 全显式收紧:default-src 'none' + connect-src 'none' + sandbox;navigate-to 经实测为 Chromium 无效指令,已弃用
  3. 文件句柄身份校验(bigint stat 快照对比)+ 服务根身份固定 + open 后路径重验
  4. 预览页持续导航守卫(外置 Chrome 精确 origin 强制 + RSB will-navigate/弹窗拦截,origin 含端口精确比较)
  5. stale guard handover(预览 tab 被导航走后守卫自动移交,不再残废)
  6. WebRTC 外泄通道关闭:CSP 管不住 RTCPeerConnection 的 ICE/STUN/TURN 流量,预览页以页面级注入(外置 addInitScript / RSB session preload)遮蔽构造函数;--disable-webrtc 经实测无效,未采用
  7. 预览 tab 撤销/退出清理双后端覆盖(外置 Chrome + RSB 侧边栏)
  8. 更新器强退路径接入预览清理 本轮(round 27l)已撤出:该改动经 review 判定不属本 PR 核心诉求主路径(§9.6-3 残余判据),连同 preview-cleanup 中间模块一并移出(见「风险」节更新器声明),转后续 issue 跟踪
  9. RSB 预览标签生命周期对称化:登记表 tabId → sessionId Map(成功导航后登记、导航离开/手动关闭注销、撤销时经渲染器桥删除持久行)、以实际 did-navigate 提交事件维护 provenance(用户地址栏导航不再留下会被误删的登记)、撤销代际(generation)防导航-撤销竞态
  10. 重启后旧预览 URL 复用防护:预览守卫只放行当前预览服务实际授权的 origin,残留的旧 URL(端口可能被其他本地进程占用)一律 stop + 停放 about:blank(fail-closed)

UI 变化

  • 用户可见变化:右栏侧边栏浏览器(RSB)的标签页在应用崩溃/强杀后不再复活已失效的本地预览标签(预览 origin 是进程内 tokenized 临时会话,24h TTL + 随机端口,重启后必然失效;此前残留的持久行会在下次启动反复出现一个停在 about:blank 的空白标签)。正常网页标签的持久化行为不变。
  • 是否存在 breaking change:无。预览标签的持久化被移除(它们在重启后本来也无法使用),其余 tab 生命周期不变。
  • 引用的设计规范:docs/design-rules/DESIGN.md §标签与导航(Pill-shaped tab 视觉与 tab 分组语义,见 §"Pill-shaped tab selectors")——本改动不改变标签的视觉样式与 tab 栏结构,仅调整标签持久化边界(进程内临时会话不入持久 store),遵守"标签代表可恢复的会话"约束的同时将不可恢复的临时预览会话排除在持久化之外。

SSH 远程会话适配

SSH 远程会话下 previewLocalHtml 返回 fail-closed 的 UNAVAILABLE(本地预览服务无法读取远端工作目录),不泄露数据。适配方向三选一结论:明确降级 + 跟踪讨论远程侧预览方案——见关联 issue #2000(本 PR 不扩展 SSH 实现)。

风险

  • 影响范围:仅启用 previewLocalHtml 时生效:启动一个 127.0.0.1 随机端口 listener,并向 SSRF 策略授予该精确 origin。不调用时行为与之前完全一致(默认配置不含任何本地网络放行)。预览页带 CSP + 无 fetch + 无 WebRTC + 导航锁定在预览 origin,无外泄通道。
  • 行为变化:托管浏览器/RSB 侧边栏(agent 专用)禁用 WebRTC——自动化场景无此需求;用户日常 Chrome 不受影响。
  • 回滚 / 降级方式:回退本分支提交即可;无数据迁移。预览授权与 token 为进程内状态;RSB 标签持久行经渲染器桥在撤销时删除,重启后残留的旧预览 URL 会被守卫 fail-closed 拒绝加载(不会载入新端口占用者内容)。
  • 已确认:sync.mjs 的 Windows 兼容修复拆为独立 PR chore(browser-runtime): make sync.mjs runnable on Windows #1834(已合并,本 PR 不含)。
  • 更新器改动已整体撤出(round 27l):本 PR 不再包含任何更新链路改动updateService.ts 已恢复原状),原 round 19 的裁决请求撤回。此前经 preview-cleanup 中间模块接入的「强退时清理预览」能力待提 issue 跟踪(feat(browser): add sandboxed local HTML preview #1803 合并后提交)。撤出同时移除了 preview-cleanup.ts(其唯一消费者即 updateService,撤出后为死代码)与 browser.ts 中的注册块——共 3 文件,纯撤出无新增改动。

已声明残余(有意不修,经审查循环评估后记录)

以下为评审循环中经过评估、有意不修的已知理论边界,均已在对应 review thread 中声明(不阻塞本 PR,部分转后续 issue 跟踪):

  1. 祖先链再次换入需句柄级 containment(Codex P1,thread XCGOO):逐级 lstat 校验后并发进程仍可能换入祖先 symlink,彻底修复需要 Windows 没有的 openat/O_NOFOLLOW 等价物(句柄级逐级打开)。当前 lstat 链 + realpath 重验 + postStat 绑定已把窗口压缩到微秒级多步时序,有意不修;根治需平台级 API 支持。

  2. 撤销→标签关闭之间的窗口(Codex P1,thread XAkmq):vendored 持久守卫无法实时读取运行时策略(policy 按调用传入、无全局注册表),撤销到标签失效之间是理论窗口。当前缓解:撤销与关闭绑定同一时刻 + 关闭等待有界,有意不修;如维护者认为必须闭合,需上游开放策略读取接口。

  3. WebRTC 闸失败结果不入 open 应答(Codex P2,thread XBh3T):把闸安装结果纳入 open 应答需改 renderer tab-op 协议,超出本 PR 范围。当前 fail-closed 已保证页面不会加载无防护内容(停在 about:blank),有意不修。

  4. SSH 远程会话适配(Codex P1,thread XDFRt):fail-closed 拒绝(UNAVAILABLE),完整支持需远端文件物化/流式到本地,属独立新功能,有意不修;见上「SSH 远程会话适配」节。

  5. 授权发布先于清理的串行化(Codex P1,thread XPIed):前提已被 round 27f 消解(预览标签不再持久化、hydrate 过滤遗留行),实际窗口已无实体可命中;若仍有顾虑可在后续 issue 跟踪「授权发布与清理的串行化」,不阻塞本 PR。

  6. 端口复用破坏代际隔离(Greptile P1,thread QDt-):刚释放的随机端口被下一代预览复用时,旧 closePreviewTabs 按 origin 匹配会误关新代同 origin 标签。极小概率边角(随机端口恰好立即复用 + 代际交替瞬间同发),完整修法需将 generation 贯穿登记项与双 sweep(改动大、收益极低),按 WORKFLOW §9.6 判定为已声明残余,转后续 issue 跟踪,不阻塞本 PR。

  7. 更新强退时清理预览(round 27l 撤出项):原经 updateService force-quit 路径接入的预览清理能力本轮整体撤出(唯一冲突文件 + 高危门禁裁决悬置),待提 issue(feat(browser): add sandboxed local HTML preview #1803 合并后提交)。撤出纯移除,未引入新行为。

  8. 侧栏宿主迁移时预览标签消失(Codex P2,thread XQGwp):预览标签刻意不落库(round 27e 防失效 URL 复活空白标签),但内嵌↔弹出切换会 invalidateSessionCaches + 释放 WebView 池,新 renderer 只能从 DB hydrate → 预览标签在宿主迁移瞬间消失。修复需改变「ephemeral 不落库」持久化策略(落库 + 按 token 生命周期冷启动清理),属对 round 27e 方案的重新设计,超出本 PR 范围;本项 P2、场景稀有,按 §9.6-3 登记残余,待提 issue(feat(browser): add sandboxed local HTML preview #1803 合并后提交),不阻塞本 PR。

  9. 端口复用 + 旧同源页 BroadcastChannel 外泄(Codex P1,thread Xz_3v):随机端口被其他本地服务占用且其页面仍开着时,预览脚本可借 BroadcastChannel 与旧页通信(旧页有不受限出站)。修复需关闭/隔离同源旧页面或改隔离 partition,触及浏览器实例创建模型(成本中高);场景需端口复用 + 旧页存活 + 恰好用 BroadcastChannel 三巧合同时发生。与残余第 6 条「端口复用破坏代际隔离」(Greptile P1,thread QDt-)同主题,同域同处理,判定为已声明残余,待提 issue(feat(browser): add sandboxed local HTML preview #1803 合并后提交),不阻塞本 PR。

  10. 清空激活标签未同步持久化(Codex P2,thread XzOZL)setActiveTab(sessionId, null) 清空选择时跳过 ipc.setActive({id:null}),SQLite 保留旧 active。场景(暂时清空选中但保留标签)不属本 PR 核心主路径,按 §9.6-3 登记残余,待提 issue(feat(browser): add sandboxed local HTML preview #1803 合并后提交),不阻塞本 PR。

  11. 转换与拖动排序并发时位置写回旧值:边界转换的异步段结束后写库仍带调用时的 position,期间若发生 reorder 会把刚落库的新顺序改回旧位置;失败回滚路径还可能按旧下标覆盖到另一个标签。完整修复需要先定清转换与 reorder 之间的排序语义和失败重排语义,不是改一个下标能了事的,故登记为已声明残余,待本 PR 合并后提跟踪 issue。

  12. closeTab 接管删除失败后的所有权:转换在途时 closeTab 接管删除并提升代次让旧转换放弃;若接管的删除自身重试耗尽,旧转换已被剥夺所有权而无人补偿,旧行残留。触发需「转换在途 + 用户关标签 + 数据库持续失败」三者同时,属错误路径边角;接管本身修复的是更易触发的两条件场景(净收益为正)。登记为已声明残余,待本 PR 合并后提跟踪 issue。

Agents can now verify workspace-local HTML pages (screenshot/snapshot)
through a managed loopback preview server instead of escaping to a bare
Chrome process (which historically led to killing the user's browser via
taskkill /F /IM chrome.exe).

Security model:
- new previewLocalHtml action + token-scoped loopback HTTP server
  (127.0.0.1:<random port>, 256-bit capability token per preview)
- serving root = the entry's directory; per-request path re-validation
  (lexical + realpath nearest-existing-ancestor), dotfile/extension
  whitelist, GET/HEAD only, nosniff + no-store + CSP
  (connect-src 'self'; form-action 'none')
- SSRF exception is exact-origin only (scheme+host+port) via the upstream
  allowedOrigins/resolveSsrFPolicyForUrl primitives, wired through
  LOCAL_PATCHES; other loopback hosts/ports and file:// stay blocked
- origin grant is revoked on listener error/close (incl. crash) so a
  freed port is never trusted by policy

Also fixes sync.mjs on Windows (drive-letter paths for msys tar, path.sep
normalization for LOCAL_PATCHES keys and the leaf prefix filter).

Fixes makecindy#1766

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 10:20
@happy9zhang
happy9zhang requested a review from a team as a code owner August 5, 2026 10:20
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 新增受控的本地 HTML 预览服务,并将其接入外置 Chrome 与 RSB 侧栏浏览器,同时加入路径隔离、CSP、WebRTC 禁用、精确 Origin 授权和预览标签撤销清理。

  • 通过 token 化 loopback 服务提供工作区内 HTML 与相对资源
  • 为两种浏览器后端增加持续导航守卫和失败关闭逻辑
  • 将不可恢复的预览标签排除在 RSB 持久化之外,并补充并发清理与代际处理

Confidence Score: 4/5

当前 PR 尚不宜合并,因为外置 Chrome 的预览初始化失败可留下无导航守卫的活标签,且此前报告的端口复用跨代误关问题仍未解决。

WebRTC 初始化脚本安装失败时,代码移除 route guard 后将异常交给会吞掉非策略错误的创建流程,最终仍可能返回该页面;此外,旧代清理仍只按 Origin 匹配,在端口立即复用时会命中新代预览资源。

Files Needing Attention: packages/browser-control-runtime/src/_generated/extension/src/browser/pw-session.ts; apps/desktop/src/main/mcp-integrations/browser-preview-tabs.ts

Security Review

外置 Chrome 的 WebRTC 初始化脚本安装失败路径仍会留下已解除导航守卫的存活标签;该路径需要像预览 goto 失败一样直接关闭页面。另有此前已报告的端口复用代际隔离问题仍然存在。

Important Files Changed

Filename Overview
apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts 新增 token 化 loopback 文件服务、路径与句柄身份校验、严格 CSP 和 Origin 生命周期管理。
apps/desktop/src/main/mcp-integrations/browser-preview-tabs.ts 新增双后端预览标签登记与撤销清理,但已报告的同端口跨代匹配问题仍然存在。
apps/desktop/src/main/mcp-integrations/browser-backend/preview-guard.ts 为 RSB 预览页增加提交级身份维护、实时 Origin 授权、导航拦截和 WebRTC 初始化保护。
apps/desktop/src/main/mcp-integrations/browser-backend/rsb-webview-backend.ts 将预览防护、撤销 generation 和标签 provenance 接入 RSB open/navigate 生命周期。
packages/browser-control-runtime/src/_generated/extension/src/browser/pw-session.ts 外置 Chrome 获得持续 Origin 守卫和预览失败关闭,但 WebRTC 初始化失败仍可能返回无守卫活标签。
apps/desktop/src/renderer/features/right-sidebar/store.ts 将预览标签设为 ephemeral,并通过 revision 与串行队列协调持久行转换、关闭及并发状态更新。

Sequence Diagram

sequenceDiagram
  participant Agent
  participant Preview as Local Preview Server
  participant Policy as SSRF/Origin Policy
  participant Browser as Managed Browser
  Agent->>Preview: previewLocalHtml(workspace HTML)
  Preview->>Preview: 校验路径并签发 token URL
  Preview->>Policy: 授权精确 loopback Origin
  Agent->>Browser: 打开预览 URL
  Browser->>Browser: 安装导航守卫与 WebRTC 闸
  Browser->>Preview: 加载 HTML 与相对资源
  Preview-->>Browser: CSP sandbox 响应
  Agent->>Preview: 撤销/退出
  Preview->>Policy: 移除 Origin 授权
  Preview->>Browser: 关闭对应预览标签
Loading
Prompt To Fix All With AI
### Issue 1
packages/browser-control-runtime/src/_generated/extension/src/browser/pw-session.ts:1506-1509
**初始化失败留下无守卫标签**

当预览 URL 的 `page.addInitScript` 安装失败时,此分支移除 route guard、保留页面并抛出异常;`createPageViaPlaywright` 随后吞掉该非策略异常,并可将仍存活的 `about:blank` 页面作为成功创建的标签返回,导致后续导航不再经过预览 Origin 守卫且 WebRTC 闸也未安装。

**How this was verified:** 跟踪该异常可见此处分支未关闭页面,而上层仅重新抛出策略拒绝类错误并继续返回页面 target。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (48): Last reviewed commit: "fix(browser): serialize preview URL tran..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts
Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5eeed534df

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

本 PR 为 cindy_browser 增加受控的本地 HTML 预览通道:通过桌面端 main 进程启动 token 化的 loopback 预览服务,向现有 SSRF 策略授予**精确 origin(scheme+host+port)**例外,使 agent 能在受管浏览器中打开工作区内生成的 HTML 并继续复用现有 snapshot/screenshot/act 流程,从而避免回退到裸跑 Chrome。

Changes:

  • 新增 previewLocalHtml action:L2 MCP 负责参数/会话上下文校验并向 host 申请预览 URL,再通过 runtime open/navigate 打开。
  • 桌面端新增本地预览 HTTP 服务:监听 127.0.0.1:<ephemeral>,按 token+目录根提供静态资源,配合 runtime SSRF allowedOrigins 做精确放行。
  • 补齐 vendored SSRF 适配与导航级测试;同时修复 scripts/browser-runtime/sync.mjs 的 Windows(msys) 路径/patch key 兼容。

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/browser-runtime/sync.mjs 修复 Windows(msys) 下 tar/路径与 patch key 匹配,确保 _generated 可复现生成
packages/lizi-mcps/src/types.ts Browser MCP deps 增加懒加载会话上下文与本地预览 URL 发行接口类型
packages/lizi-mcps/src/providers.ts 注入 getSessionContext,确保 tool-call 时能拿到真实 workingDir
packages/lizi-mcps/src/browser/tools.ts 新增 previewLocalHtml action 处理与错误返回语义
packages/lizi-mcps/src/browser/prompts/rules/browser-workflow.md 更新 agent 工作流文档,引导使用本地预览而非 file:///裸跑 Chrome
packages/lizi-mcps/src/browser/browserMcpServer.test.ts previewLocalHtml 增加 L2 行为测试(无 workingDir/无注入/打开方式等)
packages/browser-control-runtime/upstream/browser-runtime.lock.json 更新 vendored 补丁记录与 contentHash
packages/browser-control-runtime/src/types.ts 新增本地预览相关 errorCode(L2 产生)
packages/browser-control-runtime/src/shim/security-runtime.ts 透传导出 resolveSsrFPolicyForUrl
packages/browser-control-runtime/src/shim/_local/config-types-browser.ts SSRF 配置类型新增 allowedOrigins(精确 origin allowlist)
packages/browser-control-runtime/src/_generated/extension/src/sdk-security-runtime.ts 生成层补齐 resolveSsrFPolicyForUrl re-export
packages/browser-control-runtime/src/_generated/extension/src/infra/net/ssrf.ts 生成层补齐 resolveSsrFPolicyForUrl re-export
packages/browser-control-runtime/src/_generated/extension/src/browser/navigation-guard.ts 导航 guard 在私网判定前按 URL 解析 allowedOrigins(每次跳转重算)
packages/browser-control-runtime/src/_generated/extension/src/browser/config.ts 解析 host config 时保留 allowedOrigins,避免被 resolver 丢弃
packages/browser-control-runtime/src/tests/navigation-allowed-origins.test.ts 增加导航级 exact-origin allowlist 测试(同 host 不同端口/不同 scheme 等保持 fail-closed)
apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts 新增 token 化 loopback 预览服务(路径边界/扩展名白名单/CSP/生命周期撤销)
apps/desktop/src/main/mcp-integrations/browser.ts 将预览服务接入浏览器运行时配置热更新,并暴露 createLocalPreviewUrl 给 MCP
apps/desktop/src/main/mcp-integrations/browser-managed-config.ts buildManagedConfig 支持注入 previewOriginsssrfPolicy.allowedOrigins
apps/desktop/src/main/mcp-integrations/tests/local-html-preview-server.test.ts 覆盖预览服务的路径安全、白名单、CSP/headers 与 origin grant 生命周期
Suppressed comments (1)

apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts:196

  • close 事件里只做了 revokeOrigin(),但没有重置 starting/server 或清空 token。若 listener 因异常关闭(非 dispose),origin 会被置空但 starting 仍是已 resolve 的旧 Promise,后续 ensureStarted() 会直接返回旧 origin,导致发放的预览 URL 指向已释放端口(且 tokens 也永远无法再访问)。建议在 close 时清空 tokens 并把 starting/server 置空,允许后续自动重启。
        // Covers normal dispose AND listener crash — never leave the SSRF
        // policy trusting a port nobody is serving (a local process could
        // otherwise bind the freed port and intercept the token URL).
        revokeOrigin();
      });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts Outdated
Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts Outdated
Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts Outdated
Comment thread packages/lizi-mcps/src/browser/tools.ts Outdated
Comment thread packages/lizi-mcps/src/types.ts
Comment thread packages/lizi-mcps/src/browser/prompts/rules/browser-workflow.md Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

⏸️ 本 PR 触发了维护者确认门(product: feat 类型 + arch: 核心路径大改动),已创建讨论 issue 等待维护者确认。

确认方式:维护者直接在本 PR 上 Approve 即放行;需要修改请 Request Changes,改完再 Approve。

当前状态:等待维护者确认。

讨论 issue:#1808

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) labels Aug 5, 2026
…eview

Review findings (Greptile on makecindy#1803) — both confirmed:
- reject hidden directory SEGMENTS (not just dotfiles), so .git/.config/…
  content matching the extension whitelist is unreachable
- resolve the real path and open by file descriptor, then re-verify the
  handle (fstat), closing the check-then-open race where a concurrent
  directory swap could redirect the read outside the serving root
- normalize the serving root to its real path (8.3 short names on win32)
- 500 (not hang) on any internal request error

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 11:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 737be2093b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (5)

packages/lizi-mcps/src/browser/prompts/rules/browser-workflow.md:63

  • 这里写“禁止向外网发数据(connect-src 'self')”容易让人误以为完全无外联,但当前服务端 CSP 仍放行了 https:(default-src),并不等价于“禁止外网”。建议把表述改为和实际 CSP/安全边界一致(或在 CSP 收紧后明确说明仅允许同源资源)。
- 只支持 `.html` / `.htm` 入口;入口同目录下的相对 CSS / JS / 图片 / 字体资源可以加载。
- **不要**尝试 `file://` 导航(策略拒绝),也**不要**通过 shell 裸跑 Chrome 截图——受管浏览器有护栏,裸跑会失去一切保护(历史上曾因此误杀用户浏览器)。
- 预览页禁止向外网发数据(`connect-src 'self'`);依赖外网 fetch / WebSocket 的页面需把资源放到本地再预览。

packages/lizi-mcps/src/browser/tools.ts:278

  • previewLocalHtml 已新增 BROWSER_RUNTIME_LOCAL_PREVIEW_UNSUPPORTED_FILE,但这里仅识别 PATH_NOT_ALLOWED,导致宿主抛出 UNSUPPORTED_FILE: 时仍被映射成 UNAVAILABLE,错误语义会丢失。建议按 LocalPreviewError 的 code 前缀再补一档映射。
    const message = err instanceof Error ? err.message : String(err);
    const code = message.includes('PATH_NOT_ALLOWED')
      ? 'BROWSER_RUNTIME_LOCAL_PREVIEW_PATH_NOT_ALLOWED'
      : 'BROWSER_RUNTIME_LOCAL_PREVIEW_UNAVAILABLE';
    return errorResult('previewLocalHtml', `本地预览创建失败: ${message}`, code);

apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts:115

  • 当前 CSP 使用 default-src 'self' data: https:,即使 connect-src 'self' 也仍允许页面通过 <img src=https://...> / <script src=https://...> 等非 connect-src 通道对外发起请求,和 PR/文档里“不能向外网发数据”的安全模型不一致。建议把外网源从 CSP 中移除,并显式收紧 img/script/style/font/media 等指令,仅允许同源(必要时允许 inline)。
const CSP =
  "default-src 'self' data: https:; connect-src 'self'; form-action 'none'; base-uri 'none'";

apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts:10

  • 头注释写“token 绑定 serving root + session identity”,但实现侧并未在请求中校验 sessionId(handleRequest 只按 token 查 root)。建议避免在注释里宣称有 session 绑定,以免误导安全审计。
 *  - each preview gets an unguessable 256-bit capability token bound to the
 *    entry's canonical directory (the serving root) + session identity;

apps/desktop/src/main/mcp-integrations/browser.ts:109

  • applyPreviewOrigins 这里用 setBrowserControlRuntimeConfig(buildManagedConfig(...)) 直接整对象替换 runtime config,会覆盖/丢失 runtime 在运行期通过 mutateConfigFile 写入的配置(例如 gateway.auth 自动生成 token、profile 端口/颜色分配等)。这可能导致 auth 反复重置或影响已有的 loopback client。建议改成“在现有 config 上做最小 patch(仅更新 ssrfPolicy.allowedOrigins)”,而不是整对象覆盖。
const localPreviewServer = createLocalPreviewServer({
  logger,
  applyPreviewOrigins: (previewOrigins) => {
    setBrowserControlRuntimeConfig(buildManagedConfig({ previewOrigins }));
  },

@MagicLizi

Copy link
Copy Markdown
Contributor

@happy9zhang 👋 这个 PR 还有 8 条 review conversation 没 resolve(apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts / packages/lizi-mcps/src/browser/tools.ts / packages/lizi-mcps/src/types.ts / packages/lizi-mcps/src/browser/prompts/rules/browser-workflow.md),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

…or mapping

Follow-up to the 8 unresolved review conversations on makecindy#1803:
- CSP: drop https: from every directive; preview pages now load local
  resources only (script/style/img/font/media 'self' + inline + data:),
  closing the third-party-script injection + exfiltration path
- SSH remote sessions: reject previewLocalHtml when the session has a
  remoteHostId (workingDir lives on the remote host)
- capability tokens now expire (TTL, default 24h) and the comment no
  longer over-promises session-bound semantics
- map UNSUPPORTED_FILE host errors to their dedicated error code instead
  of collapsing into UNAVAILABLE
- browser-workflow.md wording matches the actual CSP

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 12:39
@happy9zhang

Copy link
Copy Markdown
Contributor Author

已修复全部 8 条 review 意见(commit 2f9f9ce7 + 737be209),对应关系:

  • 隐藏目录文件可被外传(Greptile P1)→ 隐藏路径段一律拒绝,新增测试
  • 文件打开路径竞态 TOCTOU(Greptile P1)→ realpath 规范化 + 文件句柄(fd)打开 + fstat 复核,新增测试
  • 远程子资源外传(P1 ×2)→ CSP 全面收紧:不再允许 https 子资源,仅本地资源 + 内联 + data:,新增测试
  • SSH 远程会话 workingDir(P1)→ 远程会话直接拒绝并明确报错,新增测试
  • async handler 未捕获 rejection → 请求处理器兜底 500,不再挂起
  • UNSUPPORTED_FILE 误报 → 专用错误码映射
  • token 注释过度承诺 → 增加 TTL(默认 24h),注释与实现一致,新增过期测试
  • browser-workflow.md 表述 → 与真实 CSP 对齐

测试:preview server 11/11、mcps 440、desktop typecheck 全部通过。

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/lizi-mcps/src/browser/tools.ts:289

  • 这里通过 message.includes(...) 来推断 host 侧错误码比较脆弱:一旦错误信息正文里偶然包含这些子串,会被误判;同时 LocalPreviewError 还会抛 NOT_FOUND:,当前会落到 BROWSER_RUNTIME_LOCAL_PREVIEW_UNAVAILABLE,对调用方语义不准确(缺失/非文件更像参数错误)。建议按消息前缀严格解析 code,并为 NOT_FOUND 映射到更合适的错误码。
    const message = err instanceof Error ? err.message : String(err);
    const code = message.includes('PATH_NOT_ALLOWED')
      ? 'BROWSER_RUNTIME_LOCAL_PREVIEW_PATH_NOT_ALLOWED'
      : message.includes('UNSUPPORTED_FILE')
        ? 'BROWSER_RUNTIME_LOCAL_PREVIEW_UNSUPPORTED_FILE'
        : 'BROWSER_RUNTIME_LOCAL_PREVIEW_UNAVAILABLE';
    return errorResult('previewLocalHtml', `本地预览创建失败: ${message}`, code);

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f9f9ce7ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/local-html-preview-server.ts
The sync.mjs Windows compatibility fixes moved to their own PR
(makecindy#1834); this PR keeps only the LOCAL_PATCHES allowedOrigins wiring.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/desktop/src/renderer/features/right-sidebar/store.ts:47

  • 这里的 isSandboxPreviewUrl 注释写“Mirror the main-side shape check”,但实现只判断 protocol/hostname/pathname,没有像 main 侧 isPreviewUrl 那样 fail-closed 拒绝 userinfo 变体(parsed.username/password === '')。建议与 main 侧保持一致,避免两处“预览 URL”判定语义漂移、后续维护误判。
    return (
      parsed.protocol === 'http:' &&
      parsed.hostname === '127.0.0.1' &&
      /^\/preview\/[a-f0-9]{64}\//.test(parsed.pathname)
    );

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae2f77a9de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

…eview round 27k)

round 27f skipped only addTab's first upsert; subsequent writes re-created
the persisted row: patchTabState (title/favicon reports) upserts again,
setActiveTab hits NOT_FOUND (no DB row) and rolls back, reorder references a
missing id (codex-connector P2, round 27k).

- ephemeralTabIds Set marks a sandbox-preview tab at addTab time.
- patchTabState / setActiveTab / closeTab skip the IPC write for ephemeral
  tabs; reorderTabs persists only the non-ephemeral ids.
- forgetClosedTab drops the ephemeral marker; _resetStore clears the set.
- tests: preview tab addTab/patch/setActive/close all stay memory-only.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Comment thread apps/desktop/src/main/mcp-integrations/browser-preview-tabs.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

apps/desktop/src/main/mcp-integrations/browser.ts:514

  • 这里的 /** ... */ 注释块没有挂在任何声明上(与下面的 clearStaleServiceWorkers/closePreviewTabs 都隔了一段),容易被误认为是 JSDoc 且在文档/规则检查里无效或触发告警。建议改成普通块注释(/* ... */)或移动到要描述的函数上方。
/**

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1753c94ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/browser.ts
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
… reject userinfo variants in the renderer preview check (review round 27l)

- createLocalPreviewUrl: if the RSB partition SW clear fails or times out,
  the origin was ALREADY published via applyPreviewOrigins() by
  ensureStarted() — leaving it granted would let a stale scope=/ SW
  intercept /preview/<64hex>/... before token validation and answer with a
  no-CSP page. dispose() now revokes the grant and closes the listener
  (codex-connector P1, round 27l).
- renderer isSandboxPreviewUrl mirrors the main-side userinfo rejection so
  http://x@127.0.0.1:.../preview/... is not mis-marked as a preview tab for
  ephemeral/hydrate-purge decisions (Copilot P2, round 27l).

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edce0be23a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
…(path B)

Round 27 收敛决策:updateService.ts 的强退清理预览改动整体撤出本 PR——
①它是唯一冲突文件(vs upstream/main),撤出即解除 CONFLICTING ②其高危
门禁裁决(cindy-updater.md,round 19 流程违规)悬而未决、无维护者回应,
撤出后不再阻塞本 PR ③符合 §9.6-3 残余判据(强退清理不在预览主路径)。

- updateService.ts 恢复为 merge-base 版本(纯撤出,无任何调和优化)
- 删除 preview-cleanup.ts(唯一消费者是 updateService,撤出即死代码)
- browser.ts 移除 setPreviewCleanupImpl import 与注册块(同逻辑已存在于
  disposeBrowserRuntime,不损失能力)

强退时清理预览能力另行跟踪(残余 issue 起草中)。

Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
…ndary and sync persisted active on ephemeral close (round 27l XQory/XQGws)

- XQory: ephemeral identity is no longer frozen at addTab time. patchTabState
  now re-derives it from the state URL on every web-browser patch: preview →
  normal restores persistence (row re-created), normal → preview marks the tab
  ephemeral and clears the stale persisted row (cleanupOrphanTabRow). Fixes the
  resurrect-after-hydrate / host-migration misclassification.
- XQGws: closing an ephemeral (preview) tab no longer short-circuits the whole
  persisted-active sync block. The replacement active is written to the DB when
  it is a persistent tab (skipped only when the survivor is itself ephemeral,
  which has no row). Fixes hydrate jumping back to a stale active.

Verified: store 68/68, right-sidebar+mcp-integrations 1122/1122, typecheck 0.
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e12efc64d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
…ist activation on preview→ordinary transition (round 27l Xw9-h/Xw9-l)

- Xw9-h: converting a normal tab to a preview URL now settles that tab's
  queued state writes (settleTabStateWrites) before cleanupOrphanTabRow deletes
  the row, so a pending upsert can no longer recreate the supposedly-ephemeral
  tab's persistent row after the delete (renderer migration / restart restored
  the old ordinary state).
- Xw9-l: when the ACTIVE preview tab navigates to an ordinary URL, the
  conversion now persists activation (ipc.setActive) after removing the
  ephemeral marker and re-creating the row — otherwise SQLite kept the old
  active and host detach/reattach or restart switched back to it.
- syncEphemeralStatus is now async and only invoked when the URL actually
  crosses the preview boundary (non-boundary patches like title/favicon no
  longer touch the write queue).

Verified: store 70/70, right-sidebar+mcp-integrations 1124/1124, typecheck 0.
Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Comment thread apps/desktop/src/main/mcp-integrations/browser-backend/preview-guard.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b914de8b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/browser-backend/preview-guard.ts Outdated
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts
…it when a navigation fails/stops (Greptile P1 XzI5E / codex-connector P1 XzOZH)

The identity was cleared in did-start-navigation (navigation START). A loadURL
that later fails/times out/stops never commits, leaving the OLD preview
document alive but disarmed — a later page-initiated escape would then pass
will-navigate (no identity, no preview shape). Fix: clear the identity only
on did-navigate (main-frame COMMIT), when the old preview document is
confirmed replaced. A failed navigation never fires did-navigate, so the
identity stays armed and the guard keeps blocking escapes.

Also fixes the CI typecheck error in store.test.ts (mockImplementationOnce
signature) and adds a regression test for the failed-navigation case.

Verified: full monorepo typecheck 0; preview-guard 13/13, store 70/70,
right-sidebar+mcp-integrations 1125/1125.

Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: faa2cec535

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/mcp-integrations/browser.ts
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
…fails, and skip superseded URL-boundary writes (round 27l Xz_39/Xz_34)

- Xz_39: normal→preview conversion whose orphan-row delete exhausts retries
  now rolls back the ephemeral marker and throws — no more 'mark ephemeral but
  report success' leaving a stale ordinary row that hydrate's preview filter
  cannot purge (migration/restart resurrects it).
- Xz_34: after syncEphemeralStatus awaits (settle/delete/setActive), re-read
  the tab's current URL; if a newer patch already crossed the preview boundary
  again, skip the stale write — otherwise cache shows the new URL while SQLite
  holds the obsolete one (detach/restart restores wrong state).
- Also restores the ephemeralTabIds.add() that was dropped from the
  normal→preview branch during the Xz_39 edit (identity must be marked before
  the awaited operations).

Verified: store 72/72, right-sidebar+mcp-integrations 1127/1127, monorepo
typecheck 0.

Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62225c61bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
…ls, and persist active only after the row upsert (round 27l X1gEf/X1gEa)

- X1gEf: normal→preview conversion whose orphan-row delete exhausts retries
  now rolls back the CACHE url to the ordinary URL (not just the ephemeral
  marker). Without it, a later title/favicon patch (no longer crossing the
  URL boundary) runs a plain upsert that writes the dead preview token back
  into the still-live DB row — the exact stale-preview persistence round 27e
  eliminated.
- X1gEa: setActive for a preview→ordinary conversion now runs AFTER the row
  upsert. Before, it ran first: main clears all active markers then throws
  NOT_FOUND (swallowed), and the later upsert created the row with
  isActive:false → hydrate restored no active tab.

Verified: store 73/73 (added order + rollback assertions), right-sidebar
+mcp-integrations 1128/1128, monorepo typecheck 0.

Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f4f44d0d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/right-sidebar/store.ts Outdated
- serialize per-tab URL-boundary transitions; keep the identity flip synchronous
- revision-check the delete itself before every attempt and after a failure
- wait for an in-flight create before deleting the converted row
- persist the tab's latest state after a transition, not the captured one
- let closeTab take over the delete while a conversion is still in flight

Signed-off-by: happy9zhang <happy9zhang@users.noreply.github.com>
Comment on lines +1506 to +1509
// route that aborts its own navigations.
await opts.page.unroute("**", handler).catch(() => {});
previewRouteGuards.delete(opts.page);
throw err;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 初始化失败留下无守卫标签

当预览 URL 的 page.addInitScript 安装失败时,此分支移除 route guard、保留页面并抛出异常;createPageViaPlaywright 随后吞掉该非策略异常,并可将仍存活的 about:blank 页面作为成功创建的标签返回,导致后续导航不再经过预览 Origin 守卫且 WebRTC 闸也未安装。

How this was verified: 跟踪该异常可见此处分支未关闭页面,而上层仅重新抛出策略拒绝类错误并继续返回页面 target。

Context Used: 使用和PR描述相同的语言进行评论 (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browser-control-runtime/src/_generated/extension/src/browser/pw-session.ts
Line: 1506-1509

Comment:
**初始化失败留下无守卫标签**

当预览 URL 的 `page.addInitScript` 安装失败时,此分支移除 route guard、保留页面并抛出异常;`createPageViaPlaywright` 随后吞掉该非策略异常,并可将仍存活的 `about:blank` 页面作为成功创建的标签返回,导致后续导航不再经过预览 Origin 守卫且 WebRTC 闸也未安装。

**How this was verified:** 跟踪该异常可见此处分支未关闭页面,而上层仅重新抛出策略拒绝类错误并继续返回页面 target。

**Context Used:** 使用和PR描述相同的语言进行评论 ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@happy9zhang

Copy link
Copy Markdown
Contributor Author

本轮推送后的状态。114 条 review thread 全部 resolve,CI 全绿,与 main 无冲突。残余项都写在 description 里,本轮新增两条,都标了合并后再提跟踪 issue。

本地验证跑了 store 82 条、right-sidebar 全量 547 条、多包 typecheck 八个包全绿,eslint 与 HEAD 基线逐字相同。有两处要说明,仓库根的 pnpm test:unit 有 2 条失败,devCliFlags 依赖本机文件系统的大小写语义,maker-corepi-agent.integration 需要真实 pi binary。我用 git stash 在 HEAD 上对拍过,两条失败一模一样,不是这个 PR 引入的。需要我在 CI 环境再核一次的话请告诉我。

现在的卡点只剩一个 approve(作者不能自批)和产品确认门 #1808。麻烦您看一下能否 approve,或者告诉我还差什么。

另外这个 PR 有 5308 行,确实偏大。您觉得我怎么做比较合适?

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef83539457

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1177 to +1178
const wasPreview = isSandboxPreviewUrl(oldUrl);
const isPreview = isSandboxPreviewUrl(newUrl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep preview identity across in-page URL rewrites

When preview HTML calls history.pushState() or replaceState() with a non-preview path, did-navigate-in-page propagates that URL through BrowserTabBody, and these shape checks classify the still-running preview document as ordinary. The store then removes its ephemeral marker and persists the rewritten loopback URL; a sidebar detach reloads it as an unrecognized 404, while a crash followed by port reuse can restore content from another local service because hydration and preview-guard no longer recognize the row. Fresh evidence after the earlier History API guard fix is that main now retains WebContents preview identity, but the renderer persistence boundary still derives identity solely from the page-controlled pathname; only a committed cross-document navigation should make this tab persistent.

AGENTS.md reference: AGENTS.md:L30-L31

Useful? React with 👍 / 👎.

Comment on lines +1237 to 1247
await enqueueTabStateWrite(
key,
{
id: tabId,
sessionId,
kind: oldTab.kind,
position: idx,
state: newState,
state: stateToPersist,
},
revision,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore ephemeral identity when the ordinary upsert fails

When a preview tab first navigates to an ordinary URL and this queued upsert fails, rollbackTabStateWrite() restores the cache baseline to the old preview URL, but the ephemeral marker removed at the start of the transition is not restored. A later title/favicon patch therefore treats that preview URL as persistent and can recreate the stale tokenized row once the database recovers, bringing back the invalid-preview hydration problem this change is intended to prevent. Catch this write failure and reconcile both the cache URL and ephemeral identity together.

AGENTS.md reference: AGENTS.md:L36-L37

Useful? React with 👍 / 👎.

const currentTab = now.tabs.find((t) => t.id === tabId);
return !!currentTab && extractStateUrl(currentTab.state) === newUrl;
});
if (!stillCurrent) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reconcile identity when a settled write rolls back

When a normal tab has an earlier state upsert in flight and that upsert fails while a normal-to-preview transition waits in settleTabStateWrites(), the write pump rolls the cache URL back to its ordinary baseline. The conversion still deletes the database row, then this stillCurrent check returns false without removing the ephemeral marker, so subsequent ordinary patches skip persistence and the tab disappears on detach or restart. Distinguish a newer URL-boundary transition from a state-write rollback and restore a consistent marker/row before returning.

AGENTS.md reference: AGENTS.md:L36-L37

Useful? React with 👍 / 👎.

@happy9zhang

Copy link
Copy Markdown
Contributor Author

更正一处表述:我上一条说「CI 全绿」不确切,落地后有一条红——Windows unit tests (2/2)

失败的是 packages/maker-coreapp-server/host.test.ts 的「re-probes MCP readiness after the app-server respawns」,一条 codex app-server 的就绪探测时序测试,与本 PR 改的 right-sidebar 没有任何代码关联。三条证据:①该文件与本 PR 改动的模块零交集;②该测试逐字存在于 upstream/main 与本 PR HEAD,非本轮新增;③同一 commit ef8353945 触发了两次运行,失败那次(run 31473861690)与成功那次(run 31473861706)一成一败,是时序性 flaky。

其余全绿:Linux unit ×2、Windows unit (1/2)、DCO、verify、verify-checks、Greptile、Desktop Git integration、pr-design-basis。

@happy9zhang

Copy link
Copy Markdown
Contributor Author

撤回这个 PR,缩小范围后重新提一个。

先说一处对不上的地方。「已声明残余」第 3 条我写的是"当前 fail-closed 已保证页面不会加载无防护内容(停在 about:blank),有意不修"。这个说法不准确。createPageViaPlaywright 里,预览守卫的安装脚本一旦失败,catch 只重抛策略拒绝类的错误,CDP/target 这类错误会被整个吞掉,然后把一个 route guard 已摘除、WebRTC 闸也没装上的标签当作创建成功返回。页面确实停在 about:blank,但"失败被报成了成功"是确凿的。给维护者的免修理由站不住,我在这里更正。

撤回的原因。这个 PR 从功能完成时的约 2400 行长到 5300 行,多出来的部分不是 #1766 要的三条诉求,而是预览功能自身副作用引发的连锁修复。为了让"预览标签重启后不复活",我把预览标签做成了不落库的临时身份,这逼着我改写了右栏标签 store 的全部写路径——而那是每个用右栏浏览器的人每次开关切拖都会走到的公共代码。这是架构选型的问题,不是实现不仔细。

另外注意到 #2445(allow localhost browser previews)用很小的改动覆盖了"能不能打开本地地址"这一层。两个方案是互补的:它解决"能不能开",剩下的需求是"开什么、谁来提供、边界在哪"。所以新 PR 只保留它不覆盖的部分。

重新提的方案(基于最新 main,短分支)。保留:HTTP 服务本体、路径 containment、token/TTL、CSP 响应头、Service Worker 清理、WebRTC 遮蔽、一个最小的导航拦截(防预览页自发导航到外部 origin 把内容带在 URL 里)、previewLocalHtml action、工具描述引导。撤掉:临时身份那一套(ephemeral 标记、转换队列、代次号、browser-preview-tabs.ts)、allowedOrigins 授权链、守卫的生命周期复杂度,以及右栏标签 store 的全部改动。预览标签只在托管浏览器里打开,不进右栏侧栏——#1766 的诉求是 agent 截图验证本地 HTML,agent 截图走的就是托管浏览器,侧栏可见是额外诉求。原先打到被撤代码上的意见随之作废,保留代码上的修复会随代码带进新 PR。

有两条已知问题会在新 PR 里一并修掉,不当残余:一是上面说的守卫安装失败被吞错,二是端口复用时预览页可借 BroadcastChannel 与同端口旧页通信(换 localhost 不解决这条,需要单独处理)。

想先请您确认这个方向是否合适。确认后我关闭本 PR,并基于最新 main 开一个新的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants