Skip to content

fix(chat): enforce same-origin browser boundary - #6438

Merged
huangruiteng merged 3 commits into
loopx-project:mainfrom
wchwawa:codex/chat-browser-boundary
Oct 14, 2026
Merged

huangruiteng merged 3 commits into
loopx-project:mainfrom
wchwawa:codex/chat-browser-boundary

Conversation

@wchwawa

@wchwawa wchwawa commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Goal And Delivered Outcome

  • Outcome basis / optional anchor: Maintainer-requested fix for a reproduced browser boundary failure.
  • Goal/source and gap: A page on another loopback port could read Chat sessions and submit a Chat projection write.
  • Observable before → after: Those cross-port browser requests now fail before route dispatch; the packaged Chat page and the Vite development page still read and write through their intended paths.
  • Issue/task and intended base: No issue; main.

Author Declaration

  • Written by: OpenAI Codex (GPT-6).

Implemented against

  • Specification and revision: No written specification; the maintainer request and reproduced behavior on b4603d825 are the basis.
  • Criteria:
Criterion Disposition Symbol / path Test or command
Deny unrelated browser origins before Chat reads or writes implemented loopx/chat_server.py tests/test_chat_server_cors.py; synthetic Chrome check
Deny non-loopback and ambiguous Host headers implemented loopx/chat_server.py tests/test_chat_server_cors.py
Preserve packaged Chat and Vite development access implemented apps/presentation/dashboard/vite.config.ts; desktop dev launcher Synthetic Chrome check; Chat smokes
  • Self-check before submission: Read the Chat routes, development proxy, and desktop workspace handoff; ran focused tests, browser checks, canary, static checks, and a public boundary scan. The full repository pytest suite was started but not completed locally.

Scope And Continuation

  • Completed scope and remaining work: Chat now requires a loopback Host and a matching browser Origin. The Vite proxy forwards its own page's requests to Chat; other local browser origins are rejected. Browser integrations on another local port must use a same-origin proxy. Native clients without an Origin remain supported. This is a browser boundary, not authentication for local processes. The separate Status service retains its own policy.
  • Slice boundary / successor: Complete within Chat scope. Any Status boundary change should be reviewed separately.

Validation

  • Tested revision: 05f70a42d2ed79330430980cdeea2ccb73b68b6d.
  • Run state: finished.
  • Input classes: synthetic.
Check kind Result Public-safe evidence / limitation
unit passed 80 focused Chat HTTP tests on the tested revision, including rejected cross-port reads/writes and unchanged store state.
integration passed Chat server and action lifecycle smokes on the same source diff before the rebase; no touched files changed during rebase.
real_entrypoint passed Synthetic Chrome pages: another local origin could not read Chat directly and received 403 for proxied read/write; the Vite page read/wrote with 200/201. Packaged /chat/ loaded and read/wrote with 200/201. Browser run preceded the unrelated main rebase.
static passed Dashboard TypeScript check on the tested revision; Ruff, Mypy, semantic inventory, and contract scan on the same source diff before rebase.
integration passed loopx canary premerge --from-git-diff: 19 selected checks, zero failures on the tested revision.
unit not_run Full repository pytest was not completed locally; the stopped serial run reported 240 passed and 350 skipped, with no full-suite verdict.
  • Coverage and gaps: Focused tests cover route admission and persistence; browser checks cover direct Chat, the real Vite proxy, and the packaged page with synthetic state. The packaged page's broader UI workflow and the full repository suite remain for PR CI/review.

Frontend / Visual Evidence

  • UI impact: none.
  • Before: N/A; no rendered UI change.
  • After: N/A; browser request boundary only.
  • States and viewports shown: N/A.
  • Source data: synthetic.
  • Attention review: N/A; no layout or interaction change.
  • Craft review: N/A; no visual change.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: Core control-plane hardening; no roadmap acceptance claim.

Shared-authority RFC fixture impact

N/A; this change does not implement or claim shared Goal Authority behavior.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

Signed-off-by: wchwawa <wch19961116@gmail.com>
Signed-off-by: wchwawa <wch19961116@gmail.com>
Signed-off-by: wchwawa <wch19961116@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh.

Exact head: 05f70a4.

动机

使用本机 Chat 的用户需要让自己的页面正常读写,同时阻止另一个本地网站读取会话或写入管家消息。之前另一个本地端口的页面能读会话并写入消息;现在它被请求入口拒绝,同源的打包页与开发代理继续读写,已保存消息刷新后仍可见。不可变基线的真实浏览器跨端口读取与写入为 200/201;当前拒绝这些访问,原生存储未变化,正常两种页面与无 Origin 原生客户端均通过。本次只收紧 Chat 的浏览器边界,不认证本地进程、不修改 Status 服务策略,也不宣称完整安装后的 Tauri 或所有聊天/模型工作流已经验收。 原先的“都是 loopback”不足以证明两个网站属于同一个用户交互来源。特别是只隐藏 CORS 读响应不能撤销已经发生的服务端写入;本次必须在路由产生副作用前拒绝,而不是靠页面报错假装没有写。

改动思路

在已有 Python HTTP transport 的单一请求入口校验 Host 与 Origin,复用原消息/动作 owner;开发代理只为自己的页面转发正确来源,不另建权限或状态服务。当前独立边界覆盖 Chat 请求准入、真实存储读写、开发代理及打包页兼容;Status、运行时执行权限与安装后的完整桌面旅程保持既有 owner。 相比加独立认证服务或新的设置开关,这个修复直接使用现有 HTTP parser 和 Vite proxy。Status 原 helper 有意支持多个 loopback origin,不能用其旧答案来判断 Chat 的同源私有会话边界,也不应为 Chat 顺带改 Status。严格值的规范化保持在这个 Python HTTP transport owner,未在 Python 新建通用 TS 控制面状态决策源。

来源限制按 HTTP 的 scheme/host/port 判断,Host 与 Origin 都不能重复,非 loopback/非法端口/opaque origin 不可进入。普通原生客户端没有 Origin 时继续支持,这明确不是本地进程认证。开发页面在同一个 Vite origin 请求相对 API,代理仅识别自身实际端口与 Origin 并为后端转发正确来源;无关来源仍交后端拒绝。没有新增重填、确认或会话激活步骤。

具体改动

完整差异九条路径 +216/-50:Chat transport +47/-11;Vite 共用 proxy +20/-17;desktop dev 启动参数、README、两个既有可持续 smoke、三个 HTTP 测试文件。视觉布局、Goal/消息/动作 schema、Status CORS 与执行权限规则没有改变。

没有这项具体 HTTP 政策的书面规范。 作者说源于维护者请求,我未将这一声明当作独立 specification;已核对既有私有消息 owner、原 CORS/读写路由、真实开发入口与 Rust workspace 加载路径,并独立在不可变基线 330fb2b 复现浏览器跨端口读写。安全与兼容性预期来自这个实际缺口及既有调用者,不从新测试输出倒推规范;本评审不部署或授予合并权限。

关键代码讲解

  • _chat_authority / _chat_origin_allowed L142 将明确的三种 loopback host 及合法端口规范化为 tuple,只接受与它相同的 HTTP Origin。用户名、路径、其他域名、非法端口和重复值不会被宽松 URL 解析悄悄接受;这是地址校验,不是 Goal 状态分类。
  • ChatRequestHandler.parse_request L499 在所有 GET/POST/DELETE/OPTIONS 的 dispatch 之前完成校验。JSON、SSE 和 OPTIONS 只使用这里保留下来的 _cors_origin。跨端口写入在 body/route/store 之前即 403;原生无 Origin 请求仍走原路由。
  • chatProxy.configure L8 为四个原有 Chat prefix 复用一份配置,用自身 socket 的 localPort 与 Host/Origin 匹配,只为自己的浏览器请求重写来源。实际无关页面经 proxy 读和写都 403,实际 Vite 页分别 200/201。
  • desktop dev L47 清空直连 origin,让既有 data/chat.ts 使用相对 proxy。生产 Rust 的 workspace_origin 仍加载已分配 Chat origin 下的 /chat/;当前真实打包页面读写和恢复通过,不用 tauri boot origin 冒充已安装版本验证。

相关重构已在同一原因范围内应用:一个 parse gate 覆盖全部方法,四个 proxy 条目共享原配置,减少在各 endpoint 重新维护权限判断。两处旧 default smoke 更名或更新到真实同源 URL;README 明确跨本地端口的浏览器集成要通过同源 proxy,作者也标为 breaking change,没有隐蔽改变普通自动化义务。

对主干的风险

最强反例是另一路由仍允许跨端口写,或严格 gate 破坏正常开发/打包/原生/SSE 路径。当前测试与独立执行覆盖这两个方向:80 项 focused HTTP 测试通过;原生 Chat server/action lifecycle smoke 均通过;新 current Chat bundle 实际 build/verify,包含 TypeScript noEmit。

真实 Ego 浏览器在另一个本地端口访问完整基线服务器,读会话为 200、写 projection 为 201,确认旧缺口;同一类访问在当前直接请求无法读取,经实际 Vite proxy 读写均 403。另用真实 HTTP 比较 Host/Origin/方法与相同合成 payload,非法域名/端口、其他 loopback 名称、opaque 和重复 header 在 head 都拒绝;blocked POST 前后的 Chat 文件逐一哈希相同,不能只用“请求失败”当作无副作用证明。

正常 Vite 与打包页的浏览器 read/write 分别 200/201,消息写入实际原 store 并在真实 reload 后显示;无 Origin 的原生 POST 在 baseline/head 都 201。新出现的本地页面不会因存在于同一台机器而被信任,修改 request context 或模糊 header 也不能绕过前置 gate。拒绝后的回到正常页面/代理已证明继续成功,不是只留下阻塞 receipt。没有启动真实模型、SSH、飞书或修改活动维护 Goal。

初次 baseline server 因旧 bundle 与该 checkout 不匹配而未监听;已在那个基线完整重建并按生产入口重启,之后复现的是基线实际行为,没有关闭 bundle 验证。一次外部 API 写后的 UI 仍显示先前缓存,随后真实 reload 展示两份保存消息;未将缓存等待失败当成 HTTP 修复缺陷或捏造 UI 后置结果。完整仓库 pytest 未运行到结束,不给它通过结论。

开发 advisory 在完整 semantic 之前执行;全九文件 risk canary 为 5 direct / 19 selected / 19 executed / 0 failures / 0 manual holds。没有缩扫描根、放松预算/断言。typed-state、domain-neutrality、authority、guidance 检查没有引入共享 state enum 或 actor lifecycle:新 tuple/regex 属于 HTTP 传输校验;强制 403 的文案没有称为可忽略建议;当来源合法时更深的原权限 owner 仍独立判断操作。

未查询、轮询、等待或触发 GitHub CI。没有 opt-in/default-off 承诺,因此不编造关闭实验;当前是明确披露的浏览器默认边界变更,原生客户端和正常页面兼容性通过反向正例独立验证。

我的整体评价

APPROVE,只绑定 05f70a4。当前浏览器边界修复 goal_achieved:既减少已复现的私有读取/写入风险,也保留真实正常操作与持久回读。long_horizon preserved,未增加重进/调度/结算义务;user_experience improved,普通页面继续完成同一操作,跨端口集成有可操作的 proxy 路径。保留 native/消息格式兼容有实际 caller 依据,不需要平行权限服务。相邻统一 gate/proxy 重构已应用,进一步分层不会给这个小 transport 修复增加当前收益。Status、本地进程认证、完整安装 Tauri/模型工作流与远端合并资格仍保持各自边界;本次审批不授予合并或部署权限。

English verdict: APPROVE — 05f70a4. The immutable baseline reproduces cross-port browser session reads and message writes (200/201). The current common HTTP gate refuses unrelated or ambiguous browser authority before dispatch, and the actual blocked write leaves the message store byte-for-byte unchanged. Real Vite and freshly built packaged pages retain 200/201 and durable reload; originless native writes remain compatible. All 80 focused HTTP tests, both Chat smokes and the complete selected canary pass. This is a Chat browser boundary, not local-process authentication or a change to Status policy; no full installed Tauri/model workflow is claimed.

@huangruiteng
huangruiteng merged commit 79f1895 into loopx-project:main Oct 14, 2026
31 of 44 checks passed
@wchwawa
wchwawa deleted the codex/chat-browser-boundary branch October 15, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants