Repository navigation
fix(jev): support secure scoped capture on native Windows - #8620
xiaobaifly7 wants to merge 2 commits into
Conversation
Signed-off-by: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
本次评审对应提交 042e315368b5e9b30b985132353daa450bfc234d。
[P2] 新 Windows 采集分支还需按规范路径核验范围。
动机 / Motivation
在 Windows 或 POSIX 上运行可选 Jev drift wrapper 的操作者。主干在没有 O_NOFOLLOW 的 Windows 上显式不可用;macOS/Linux 继续沿既有 O_NOFOLLOW。原 POSIX 路径继续可用,实际 wheel 与合成 File authority 验证本地 baseline、真实 refresh 和 egress_denied 回执。新 Windows 实现保留 reparse 与身份检查;范围规范化仍需要补足。独立运行环境为 macOS。Windows 路径对照模拟 OS 命名和属性边界,未运行 Win32/NTFS、ACL 或远端 CI;本机 Windows 正常和现有 race 结果引用作者声明,不当作独立验证。Windows 分支只按输入拼写检查被排除的路径,规范路径目前只用于判断是否仍在工作区内。用等价路径解析的隔离对照可到达实际读取;作者的本机用例覆盖 reparse、替换和权限冲突,尚未覆盖这个规范路径边界。
改动思路 / Approach
Windows 必须用现有 capture owner 内的平台 IO,保留现有有界读取和原 Git/receipt 逻辑。 分支补足原生打开能力,但 scope 必须约束已打开的对象,不应只验证输入字符;两种语言的运行说明也要跟上。 评审依据 packages/loopx-jev/DRIFT_SHADOW.md 的 scoped files 合同。继续用同一个可选 Jev owner 采集文件。Windows 的句柄校验要同时守住路径范围;微软的路径命名说明 明确一个对象可以有多种路径表示。
具体改动 / Changes
关键代码讲解
POSIX-OFF-PARITY:精确 04958a89abbbf6b7d3f79327437e2d6deae97453 与 042e315368b5e9b30b985132353daa450bfc234d 的 wheel 都在 macOS 实跑 init → refresh → drain → status,并写回本地 not_evaluated/egress_denied;原 refresh 记录不变,未启用 observer 时不产生观察目录。HANDLE-IDENTITY:新分支对目录句柄、最终文件和 reparse 属性做校验。作者的本机 Windows 验证覆盖了普通文件、链接、句柄冲突和置换;我单独复跑了 portable 用例和实际 POSIX 路径。
对主干的风险 / Risks
CANONICAL-SCOPE:validate_paths 仅对输入路径的文本组件执行排除规则;resolve() 的结果只用于确认仍在工作区内。文件句柄的身份一致也不能说明其规范路径符合范围。在模拟 Windows 名称解析、使用真实 capture 和 Git 的隔离对照中,规范对象相同的两种表示给出不同结果。这个观察没有执行 Win32 或 NTFS。请在读前把同一范围规则施加到已绑定身份的规范路径,补本机 Windows 负例和普通文件对照。两份运行指南还将 Windows 写作未验证,需要与实际支持范围同步。
语义与 CI 对齐
macOS 主干 126 项通过,新头 143 项通过、4 个 Windows-only 用例跳过。两个精确 wheel 的实际 CLI、POSIX 拒绝用例、Ruff 与标准预检通过。作者说明中的 Windows 包/共享用例失败仍保留,macOS 结果不能为它们归因或视作通过;没有查询远端 CI。
我的整体评价 / Verdict
请求修改。保留现有 handle identity 与 POSIX 边界,补规范路径的统一范围校验及 Windows 本机验收。这是已有可选 Jev CLI 的平台采集路径,未改变视觉界面。未验生产安装、原生 Windows ACL 或真实模型。
English verdict: REQUEST_CHANGES. Validate the canonical opened path against the same capture scope, then demonstrate the rejection on native Windows. The packaged POSIX path and default-off behavior passed independently.
| or path.is_absolute() | ||
| or ".." in path.parts | ||
| or ".git" in path.parts | ||
| or ".git" in parts |
There was a problem hiding this comment.
[P2] 请把这个排除规则用于实际打开对象的规范路径。当前 resolve() 只检查是否仍在工作区;两种 Windows 路径表示可能指向同一对象,但只有原始字符串受规则约束。隔离的名称解析对照得到不同采集结果。请加原生 Windows 负例,确认两种表示都在读取前拒绝,并保留普通文件正例。
Signed-off-by: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>
|
Addressed
Both run guides now describe Windows support and its limits. The combined package/shared run changes from 141 passed / 10 failed / 4 errors to 146 passed / 6 failed / 4 errors: the four new negative failures are fixed and the added positive passes. The six existing failures, four SQLite setup errors and existing native mypy |
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
复审提交:be915c2b2466a7a52c4b3ab91c0c04d3b3aad477。
[P2] 缺失文件的 Git 元数据别名仍可写入观察范围。 缺失分支
动机 / Motivation
在 Windows 上用 Jev 观察所选工作区文件的开发者。 上次发现的已有文件别名现在会在读取前拒绝。但如果经同一目录别名选择的文件尚未创建,初始化会接受这个范围,文件一出现,观察器就无法形成这一轮证据。 注入 Windows 的路径映射并执行真实 Jev 和 LoopX CLI 时,缺失元数据文件的别名会保存为有效范围。文件出现后,原刷新仍写入,观察器报 capture_failed、清空基线且没有事件。 独立验证在 macOS,注入 Windows 路径及文件身份观察。作者另外报告原生 NTFS 结果。本轮未独立运行原生 Windows、ACL 或网络共享,也未查询 CI。
让初始化和后续采集对同一 Git 元数据父目录使用同一范围规则。 范围合同 允许未来出现的普通文件。更新后的 Git 元数据排除 仍需覆盖已知父目录。 核验项:opened_alias_is_rejected_before_read, excluded_scope_cannot_be_initialized。
改动思路 / Approach
沿用现有 capture、范围校验和 Windows 句柄,不引入第二套范围或证据状态。现有元数据拒绝应同时覆盖已知父目录。 保留 POSIX 路径和可选观察器边界,把修复收在 Windows 路径校验。 GetFinalPathNameByHandleW 支持已打开目录和文件。既有代码已经取得父目录句柄,缺失分支可以先检查规范位置再记录缺失。 Microsoft 文档
具体改动 / Changes
- Windows 捕获 已存在的对象在读取前核验规范路径。上一提交对已有别名返回 0 并读取了合成文件。当前返回 2,不写基线且未调用 read。
- 初始化 已有的 Git 元数据目录通过短名选中时,目标尚未创建会得到 baseline_created。目标出现后,源刷新成功,Jev 报 capture_failed、清空基线且无事件。没有读取被排除的字节。
- 英文指南、中文指南 已说明 Windows 的验证边界。真实 Jev CLI 接受一个尚未创建的普通文件。创建后产生一个本地事件,关闭外发时记录 not_evaluated / egress_denied。 PR 基点 macOS 包测试 93 项通过,当前提交 111 项通过及 12 个原生 Windows 项跳过;两边已有真实 refresh 和关闭状态用例都通过。 三个独立 CLI 用例还验证了当前已存在别名、缺失别名和正常缺失文件。
对主干的风险 / Risks
影响的是观察器证据。已知排除范围初始化成功后,常规刷新仍可能追加 Core 运行记录,而该观察器的基线被清空。原 POSIX 和关闭状态用例保持通过。Ruff、差异检查、签署、双语 RFC 和当前主干合入模拟通过。观察器初始化后范围固定,后续的 Core 刷新不会替换其中错误的文件选择。操作者需另建观察范围才能避免同一故障。
我的整体评价 / Verdict
请求修改。初始化应在已有父目录可核验时拒绝该范围,避免把不可用的选择保存到后续轮次。 在把文件记为缺失前,取最深已打开父目录句柄的规范相对路径,使用现有范围规则检查。保留普通未创建文件的正例。 建议在同一项原生 NTFS 验证中确认前述拒绝和普通路径恢复。
English verdict: REQUEST_CHANGES. Head be915c2. The existing alias is rejected before read. A missing leaf under an already excluded Git directory alias still initializes; when it appears, a real refresh succeeds but Jev loses its baseline and emits no event. Validate the canonical existing parent before accepting absence.
| try: | ||
| observed = os.lstat(target) | ||
| except FileNotFoundError: | ||
| return None |
There was a problem hiding this comment.
[P2] 已有的 Git 元数据目录通过短名选中时,目标尚未创建会得到 baseline_created。目标出现后,源刷新成功,Jev 报 capture_failed、清空基线且无事件。没有读取被排除的字节。 在把文件记为缺失前,取最深已打开父目录句柄的规范相对路径,使用现有范围规则检查。保留普通未创建文件的正例。 原生 NTFS 用真实短名选中一个不存在的 .git 子项,init 应失败且无状态。用相同方法选择普通目录的缺失文件,创建后仍产生事件。
Goal And Delivered Outcome
loopx-jev drift initoriginally rejected every native Windows workspace because Windows does not exposeos.O_NOFOLLOW. Review of the Windows implementation then found that a real 8.3 alias of.gitbypassed the requested-path exclusion and reached the file read.CANONICAL-SCOPEreview on PR fix(jev): support secure scoped capture on native Windows #8620. PR base ismain; original branch base is04958a89abbbf6b7d3f79327437e2d6deae97453. Fetchedorigin/mainat9ce3a46bfe525aa2167de6699fc676ce8ef60247merges cleanly; no rebase or history rewrite was needed.Author Declaration
Implemented against
packages/loopx-jev/src/loopx_jev/drift_capture.py(validate_paths,capture,stable_capture).O_NOFOLLOWon native Windowsdrift_capture.capture,_windows_capturetest_windows_regular_file_without_nofollow; native CLI init/refresh/status_windows_identity,_windows_descriptor,_windows_capture_windows_identity,_windows_capture_windows_descriptorusesFILE_FLAG_OPEN_REPARSE_POINT_windows_final_path,_windows_capture, existingvalidate_pathstest_windows_rejects_native_git_path_representationscreates realGIT~1aliases;test_windows_fails_closed_without_final_pathinjects Win32 API failure/insufficient-buffer results; every negative case traps any attempted read_windows_identityfields remain unchangedtest_windows_stable_capture_allows_ctime_skew, ordinary-file and directory-pinning positivesos.open(..., O_RDONLY | O_NOFOLLOW)branchtest_posix_keeps_nofollow_open; branch simulated on this Windows hostloopx-jevconsole entry point; no provider dispatch or request reservationGetFinalPathNameByHandleWwith normalized DOS names; the workspace-relative file name goes through the existing scope validator beforeread(). No runtime dependency, configuration, receipt schema or capability was added. Native Linux/macOS and Windows ACL qualification were not run.Scope And Continuation
.giteven through a real short-name alias. Failed or incomplete canonical-name queries stop capture. Both run guides now describe native Windows support and its qualification limits. Existing suite/typecheck limitations are disclosed below.Validation
regression_paritypassedtest_drift_capture.pysuite passes 30/30, without skips. Long-name, case, extended/device-prefix, short-name, API failure/truncation and all prior race/reparse cases pass.unitfailedintegrationfailed0600; the errors occur while initializing the SQLite fixture (unable to open database file), before capture. File-provider criterion-binding cases passed.real_entrypointpassedloopx-jevconsole entry point against a disposable Git workspace:drift init→ edit scoped text →drift refreshwrapping realrefresh-state→drift status→drift drain→drift status. Shadow mode,allow_egress=false, explicit disposable registry/runtime and suppressed external sinks. Result: baseline created, event queued, capture failures 0, one receipt written.real_backendpassednot_evaluated, reasonegress_denied, dispatchnot_sent; provider-attempt manifest remains empty. This qualifies local capture/receipt plumbing, not model judgment or acceptance completion. Disposable Effect runtime was stopped afterward.regression_paritypassedO_RDONLY | O_NOFOLLOW, performs the existing bounded read and never calls the Windows opener. This is simulated branch coverage on Windows; no native POSIX run is claimed.staticpartialgit diff --checkpasses. Native strict mypy reports the same existingos.O_NOFOLLOWattribute error in both the original module and the follow-up (line 196 before, line 227 after); no typecheck pass is claimed. The root mypy allowlist does not include this optional package.test_deletion_mode_and_no_final_newline_remain_visibleexpects Unix executable bits;test_private_file_permissions_and_secret_like_scope_rejectionexpects mode0600;test_evidence_only_work_is_not_dropped_as_no_code_deltaexpects LF from a Windows text-written fixture.test_committed_constructed_snapshots_match_generator_byte_for_bytecompares generated CRLF against committed LF, andtest_replay_reproduces_the_committed_live_summaryreports a recorded replay mismatch whose cause was not investigated in this capture repair.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
none.Signed-off-bytrailer.🤖 Generated with Claude Code