Repository navigation
fix(chat): enforce same-origin browser boundary - #6438
Conversation
Signed-off-by: wchwawa <wch19961116@gmail.com>
Signed-off-by: wchwawa <wch19961116@gmail.com>
Signed-off-by: wchwawa <wch19961116@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
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.
Goal And Delivered Outcome
main.Author Declaration
Implemented against
b4603d825are the basis.loopx/chat_server.pytests/test_chat_server_cors.py; synthetic Chrome checkloopx/chat_server.pytests/test_chat_server_cors.pyapps/presentation/dashboard/vite.config.ts; desktop dev launcherScope And Continuation
Validation
05f70a42d2ed79330430980cdeea2ccb73b68b6d./chat/loaded and read/wrote with 200/201. Browser run preceded the unrelatedmainrebase.loopx canary premerge --from-git-diff: 19 selected checks, zero failures on the tested revision.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
N/A; this change does not implement or claim shared Goal Authority behavior.
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).