Skip to content

fix(chat): redact encoded local path separators - #5978

Open
mikamikasuki wants to merge 7 commits into
loopx-project:mainfrom
mikamikasuki:codex/loopx-redact-encoded-separators
Open

mikamikasuki wants to merge 7 commits into
loopx-project:mainfrom
mikamikasuki:codex/loopx-redact-encoded-separators

Conversation

@mikamikasuki

@mikamikasuki mikamikasuki commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Goal: Keep protected local paths out of Chat answer streams while preserving safe project-relative filenames.
  • Expected behavior: Maintainer direction #5136, direction 3 requires public-safe path presentation; displaying a filename does not grant filesystem access.
  • Gap: The full-path matcher recognized encoded protected roots, but the streaming filter retained only a limited set of root spellings. A long root could be emitted in answer chunks before the completed answer was redacted.
  • Observable before → after: The same character-by-character probe leaked uppercase, lowercase-separator, and mixed %63 encodings on exact main 0e5acf. Main advanced to fac40bb; its intervening commits did not change loopx/chat.py or the focused path tests. On this PR head, the stream and final answer redact all three forms as [local-path].
  • Scope: Match the full path classifier literal and UTF-8 percent-encoded character forms when buffering incomplete roots. Path access, authorization, status/proposal handling, and filesystem behavior remain unchanged. The existing [Architecture]: two modules both claim to own "private-looking text" #5136 direction is the task anchor; a separate issue is not required.

Author Declaration

  • Written by: model_agent — OpenAI GPT-6 Luna Medium.
  • Some coding work was AI-assisted. I personally reviewed the changes, verified the relevant behavior and tests, and take responsibility for the submission.

Acceptance Criteria

Criterion Result Coverage
Encoded separators, root bytes, and normalized aliases cannot bypass protected-root matching Implemented redact_local_paths; tests/test_chat_path_redaction.py
Long encoded protected roots remain buffered through arbitrary stream splits Implemented Direct stream and synthetic ACP answer.delta tests
Private descendants are hidden while safe project files keep relative names Implemented Path-redaction and response-parsing tests

Changes

  • Build the streaming prefix matcher from per-character literal and UTF-8 percent-encoded alternatives, comparing percent hex without case sensitivity.
  • Retain incomplete encoded separators while chunks arrive, so the existing length fallback cannot expose a protected root prefix.
  • Add lowercase-separator and mixed encoded-root-byte regressions at both the stream filter and ACP answer boundary.
  • Preserve normalized protected-root alias matching, safe relative path display, and existing filesystem and authorization boundaries.

Validation

  • Tested head: 7be3bbbc0a91078b50fdbf4609e45a22a13c4d4b, based on current main fac40bbc43ff17d0c0d573af69204cb73b36e842.
  • Red/green evidence: On exact main 0e5acf, the character-stream probe leaked the three encoded forms. The newer main commits did not modify the affected Chat source or focused test paths. On this head, stream and ACP outputs are redacted.
  • Focused validation on the rebased head: tests/test_chat_path_redaction.py, tests/test_chat_response_parsing.py, tests/test_chat_dsh_adapter.py, and tests/test_chat_session_active_turn.py — 126 passed.
  • Static validation on this head: Ruff on the changed Python files, Python compilation, and git diff --check origin/main...HEAD passed.
  • DCO: all seven commits include a Signed-off-by trailer.
  • Hosted checks on this head: Summary passed; DCO, Dependency Review, and Release Artifacts are queued; merge-gate is expected. The previous changes-requested review refers to an earlier head; the new head is awaiting review.
  • Coverage boundary: Path inputs and ACP transport are synthetic; no live Lark send or native Windows filesystem run was performed.

UI / RFC Impact

  • UI impact: none; text redaction behavior only.
  • Shared-authority schemas and semantic dimensions: unchanged; no RFC fixture impact.

Type of Change

  • Bug fix
  • Test update

LoopX Area

  • Host or runtime integration

Boundary Checklist

@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: 41e30b7; frozen base: b2f33bc. CI was not consulted.

动机

前端与飞书 Chat 用户查看 Agent 报告中的文件位置时,会收到本机目录名或私有目录后代。
Agent 返回编码后的本机文件位置时,旧版可能展示完整路径;本改动希望把普通项目文件显示为 ./notes/report.md,把私有后代统一隐藏。
已验证短编码根的普通文件与私有后代能正确展示;含字面百分号的目录别名出现新增回归,长编码根的流式泄漏仍未修复。
不改变文件读取/写入授权、Goal 状态、Todo、执行策略或 #5136 的完整迁移边界。
当前 head 仍须修复字面 %20 私有目录的 ./ 别名回归,以及长编码根跨字符输出时提前泄漏的问题。

改动思路

继续扩展现有 Chat 展示 owner,并复用 public_safe_text 的公共路径识别;不新增状态、权限或平行 capability。根路径编码属于当前展示边界,范围与泄漏问题相称。
本 PR 的边界是当前回答、流式片段和结构化字段的路径展示;公开链接和普通相对文件名应保留,额外声明的私有根及后代始终隐藏。尚需在同一 PR 修复下述两个路径缺口;不要求完成整个 #5136 迁移。

回答从声明的项目/私有根进入 matcher,再经 raw/decoded 候选检查得到相对文件名或脱敏标签。ACP 的每个输出通知先经过流式缓冲,最终完整回答另经 parse_agent_response。两条出口必须遵守同一个隐私承诺;最终脱敏不能撤销已经显示的片段。结构化 status/proposal/gate 保持完整脱敏,无新权限或状态。

具体改动

两文件 +130/-12:chat.py 增加编码分隔符/根组件识别和候选归一化(+53/-12),路径测试增加 77 行。真实调用包括 Chat ACP、其它既有适配器和 status HTTP;没有新 UI 操作或配置。

  • _local_path_pattern,chat.py:48:把每个声明根组件展开为字面字符或 UTF-8 百分号形式,分隔符接受斜杠、反斜杠及其编码;复用通用 LOCAL_PATH_SURFACE_PATTERN。必须完成根和边界才识别,不能据此认为流式前缀也安全。
  • redact_local_paths/path_parts,chat.py:186–224:先尝试 normalized/decoded 候选,再判断私有后代,最后保留普通项目 suffix。这里取消 root 的 unquote,却继续解码 candidate,产生下面的字面百分号回归。
  • VisibleResponseStreamFilter._next_boundary,chat.py:327–368(相邻未改):160 字符 fallback 仍只保留 raw root 前缀;它是本次 matcher 改动的实际消费方。
  • ACPStdioAdapter.start_turn,chat_acp.py:322(未改):真实 initialize/session/new/prompt subprocess 的 answer.delta 与最终 parse 分开验证,非仅 mock reducer。

规范依据是 维护者 #5136 direction 3 与 冻结基线的工作区指南:普通项目位置保留相对文件名;额外私有根及后代必须隐藏;展现不授予读取/写入权限;复用公共识别并按目标选择政策。当前实现满足共享 owner/权限界限,私有后代展示仍不满足。

规范映射:https://github.com/loopx-project/loopx/issues/5136#issuecomment-5857977250;revision b2f33bc。 answer path presentation:Keep safe project-relative filenames; hide machine/external/protected descendants. disposition=not_met。 display is not authority:Status/proposals/gates fully redact; display grants no read/write permission. disposition=implemented。 one shared detection with destination policies:Reuse public_safe_text; avoid blanket public/private union. disposition=implemented。

对主干的风险

P1:字面 %20 私有目录的 ./ 别名出现新增泄漏。 合成项目 /custom-volume/project 下声明私有根 runtime%20private,输入 /custom-volume/project/./runtime%20private/gate.json。头版本把 candidate 中 %20 解成空格,但 private_roots 保留字面 %20;直接私有前缀又被 /./ 隔开,因此输出 ././runtime%20private/gate.json。冻结基线输出 [local-path]。独立探针创建同名真实目录并确认 alias.resolve() 指向同一文件,通过实际 ACP 的 final 和逐字符 delta 都复现。最小修复是让 raw/decoded root 与 candidate 的私有后代比较一致,并保留这一物理别名反例。

P1:长编码根的流式输出仍提前泄漏。 当完整编码根超过 160 字符、逐字符到达时,_next_boundary 的 partial_root 只识别 raw prefix,matcher 尚不能匹配完整根,fallback 提前释放片段。当前完整 final 正确为 [local-path],实际 ACP delta 却保留整个编码本机路径。基线也失败,明确属于本次编码保证尚未覆盖的原有缺口,不称新增回归。应把既有前缀保留逻辑扩展到 matcher 所接受的编码形态,增加长根/逐字符输出断言,不靠最终文本替换。

独立同夹具五项检查:基线短编码普通/私有、长编码三项失败,字面百分号别名和普通公开/私有控制两项通过;head 短编码两项及控制通过,字面别名回归、长编码 stream 失败。正式的路径/响应 45 项和适配器/Lark 36 项均通过,说明现有测试漏掉这两个条件。diff hygiene、语义 advisory 与全树 smoke 通过;零 vocabulary 候选不证明文本隐私等价。最小复现无需账户、模型调用或生产 Goal:在现有 ACP 合成 provider 中逐字符返回上述 alias 与 quote(long_private_path, safe=""),同时断言 final 和所有 delta。

初次 reviewer 合成夹具未按 macOS /var → /private/var canonical 根对齐,已修正根并重跑;初次 smoke 路径不存在,已定位仓库实际 smoke 重跑。这些准备失败保留,不归因 PR。Windows 原生 filesystem、真实飞书发送、打包窗口与付费模型未测;当前真实 ACP 反例已足够阻断,无需把整条迁移路线变为门槛。

我的整体评价

范围与原问题相称,现有展示 owner 足以承载;短编码路径有已验证增量,但 long_horizon/user_experience 有具体 regression,observable_semantics 为 unintended_drift。未来重构检查应在同一展示边界统一编码解释与前缀保留,这就是本次最小修复;不需要新 capability、平行 classifier 或无关 TS 改写。REQUEST_CHANGES;修复后按新的 exact head 重新验证。

English verdict: REQUEST_CHANGES — head 41e30b7. The literal-percent private-directory dot alias newly leaks; long encoded roots still leak through real ACP deltas despite a redacted final reply. CI was not consulted. No merge or live-install claim.

@mikamikasuki

mikamikasuki commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Addressed both requested changes in later commits, with the ACP regression test tightened in d92727c.

  • Protected-root comparisons cover raw and decoded component forms, so a symlink alias reached through /./ cannot expose a literal %20 private path.
  • The streaming filter retains fully percent-encoded protected-root prefixes, including partial encoded separators. The ACP regression sends one character at a time and checks both the final answer and answer.delta.

For parity, I applied the three focused regressions to a disposable checkout of reviewed head 41e30b7 without its fixes: all three failed. The dot alias was returned as ././runtime%20private/gate.json; the encoded path leaked through ACP answer.delta while the final answer was redacted; and the direct stream filter emitted a path prefix. On current head d92727c5, all three pass.

Validation on current code: 67 passed across the path-redaction, response-parsing, and DSH adapter suites; Ruff, Python compilation, and both diff checks pass. The new-head GitHub checks are running.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from d92727c to c3e30bc Compare October 8, 2026 21:11
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Follow-up fix for the same #5136 direction-3 path-presentation gap.

On exact current main 6ca04ed808d8b67efd4ff66a4486c595293f1591, with protected roots /custom-volume/project and /custom-volume/project/runtime, these aliases were returned as absolute paths instead of being hidden or rendered relative:

  • /custom-volume/./project/runtime/private/gate.json
  • /custom-volume/%2e/project/runtime/private/gate.json

The same prefixes with /notes/report.md should remain usable as ./notes/report.md. Root matching only recognized a literal protected-root prefix, so dot-segment aliases before that root bypassed classification.

The update matches absolute candidates against normalized protected-root components, preserves safe project-relative filenames, and redacts private descendants. Regressions cover both aliases, every stream split, a 190-character private suffix, and a parent-segment path. Direct current-main reproduction showed the leak; the two focused dot-alias tests failed on the previous PR head and pass with this fix.

Validation on head c3e30bcc8334c5dbaca16687d264d7285e4a7a77, rebased on canonical main 6ca04ed808d8b67efd4ff66a4486c595293f1591: path-redaction, response-parsing, ACP, and Lark private-progress suites: 96 passed; Ruff, Python compilation, targeted mypy, diff checks, and the pre-merge canary (2 selected checks) passed. Hosted checks have restarted on this head; no merge is claimed.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from c3e30bc to 16f0be4 Compare October 8, 2026 21:55
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Follow-up on the two requested findings at current head 16f0be438d7914ce43395cc1a943ec82cdd6ea72 (base 3ed5d6bc88ffaaa05f2b1bc6a98ed5d921379fd1): the literal %20 private-root alias reached through /./ is now normalized against the protected root and redacted; the >160-character fully encoded root remains buffered during character-by-character ACP deltas, and the regression asserts both deltas and final output. I reran the five focused suites on this exact head: 113 passed. Ruff, Python compilation, and git diff --check origin/main...HEAD also pass. The PR description contains the exact-main test-only parity result (9 failures/2 passes on base; 11 passes on the candidate regression selection). Hosted build, DCO, dependency review, and Summary pass; Chat bundle and Node minimum compatibility are still pending.

@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

动机

前端与飞书 Chat 用户查看 Agent 回答中的文件位置。普通项目文件应保留可读的相对文件名,声明的私有目录应在完整回答和逐字输出中都隐藏。大写编码的长私有根已隐藏;小写分隔符和混合编码字符仍在真实 ACP 流式片段中泄漏,最终回答虽然已脱敏。流式前缀保留仍未覆盖完整 matcher 接受的编码形式。不改变文件访问授权、Goal/Todo 或调度,也不要求完成 #5136 的全部迁移。

本次独立重审完整 head 16f0be438d7914ce43395cc1a943ec82cdd6ea72,冻结 base 3ed5d6bc88ffaaa05f2b1bc6a98ed5d921379fd1。没有继承旧结论,也没有查询或等待 CI。旧评审的字面 %20 私有目录 dot alias 已由本轮 113 项正式测试覆盖通过,原大写长编码根也通过;这不能证明其它已接受编码形式安全。

改动思路

现有 Chat 展示 owner 适合承载这项修复,前缀与完整匹配必须使用同一编码语言。当前 PR 的边界是完整回答与真实流式片段都正确隐藏私有路径;尚未满足这一边界。复用 public_safe_text.LOCAL_PATH_SURFACE_PATTERN 识别一般本机形态,Chat 增补调用方声明根和可读的项目相对路径政策;没有增加权限、状态或平行 capability。

已在实现前读取维护者 #5136 direction 3及冻结版本的 docs/guides/personal-workspace-user-guide.md,spec_revision 3ed5d6bc88ffaaa05f2b1bc6a98ed5d921379fd1。逐项映射:answer path presentation 仍 not_met,私有后代在真实流式出口泄漏;display is not authority 已实现,展示不授予文件访问权,结构化状态/提议/门禁继续完整脱敏;one shared detection with destination policies 已实现现有 owner 的采用,但完整 token 与增量前缀的语言仍须统一。

具体改动

完整两文件 +325/-32:loopx/chat.py +128/-21,tests/test_chat_path_redaction.py +197/-11,没有生成文件或安装指令变更。

  • _local_path_pattern(chat.py:48)为根组件中的每个字符接受字面或 UTF-8 百分号编码,编码不区分大小写,分隔符接受 /、反斜杠、%2f、%5c;新增一般绝对候选供根前 dot alias 归一化。
  • redact_local_paths(chat.py:187)比较 raw/decoded 组件,处理 ./.. 和 Windows drive 大小写,先隐藏声明的私有后代,再输出安全项目相对文件名;display_path_parts 保留显示大小写。一般无关自定义路径保留,公共 classifier 识别的机器路径仍隐藏。
  • VisibleResponseStreamFilter.__init__(chat.py:369)建立 raw、quote 和两种安全分隔符拼写列表;_next_boundary(chat.py:409)用严格 startswith 判断未完整根。超过 160 字符而未识别为前缀时,旧 fallback 会提前释放片段。完整 matcher 接受的大小写及任意逐字符编码组合远多于这个列表。
  • 测试覆盖编码分隔符、根字节、dot/parent alias、安全相对文件名、真实同一文件的 symlink alias,以及实际 ACP 子进程的逐字符通知。不是只比较最终回复;ACP 的 answer.delta 和 final 是分别消费的出口。

独立 source 五套路径/响应/DSH/Lark 测试 113 passed;diff 语义 advisory 零候选、全树语义、Ruff、原生 premerge 两项选定检查及 direct hygiene/compile/boundary 通过。零 vocabulary 候选不证明隐私等价。正常构建 fresh wheel,并验证 wheel chat.py 与精确 head SHA256 相等;源目录外的安装环境通过真实 ACP 协议重验同一四组输入。source 的验证使用仓库选定解释器,独立 wheel 使用受支持 Python 3.14.8。

对主干的风险

[P1] 长编码根的增量前缀仍漏掉小写及混合编码。 以合成根 /custom-volume/ 加 18 个 private segment 构成长私有根,将路径编码后逐字符发送。仅把 %2F 改成 %2f(400 字符),或把根中 c 改成 %63(402 字符),完整 matcher 就能在 final 输出 [local-path],但前缀列表不能保留该 token;实际 ACP 的所有 answer.delta 拼接结果是原编码路径。源码与独立 wheel 一致复现,直接 stream filter 也一致。最终替换无法撤回用户已经看到的片段。

独立固定夹具四组 base/head 对照:base 大写、小写、混合字节和部分编码都失败;head/wheel 大写与部分编码通过,小写与混合字节仍失败。这是本 PR 编码保证未覆盖的原有缺口,不称此次新增回归。113 项现有测试仍通过,说明它们不能区分这两个条件。

最小修复是在同一展示边界让增量前缀保留接受与完整 matcher 相同的编码语言,包括未完成的 % triplet;不要只补有限个 quote 拼写,也不靠增大缓冲阈值或 final 重写。加入长根小写/任意根字节/混合编码、全部 chunk split 和真实 ACP delta/final 对照,并保留 URL、普通文本、安全文件名及私有根优先级。

本轮最初的测试路径和 caller 搜索路径各有一处准备错误,已按真实仓库路径修正;wheel 第一次 probe 缺少测试 harness 的 pytest,补齐后完成,原失败均保留,不归因于 PR。未测试真实飞书发送、原生 Windows 文件系统、付费模型或打包窗口渲染;实际安装态 ACP 反例已足够阻断当前展示保证。

我的整体评价

REQUEST_CHANGES。范围与原问题相称,已有修复提供可验证的正向增量,但重复回合中的流式隐私承诺仍不成立。不能用最终正确、绿测试或已关闭旧反例宣称用户体验和长期效果已完成。

未来相关重构检查建议复用一个保留显示大小写的组件归一化 helper,并让完整/前缀识别共享同一编码规则;这是同一边界的有限简化,不需要新 capability、广泛 TS 改写或整个 #5136 前置。修复后对新 exact head 再验证。

English verdict: REQUEST_CHANGES - 16f0be4. Prior dot-alias and uppercase-root cases pass, but long lowercase and mixed encoded roots still leak through real ACP deltas despite a redacted final answer. The same failure reproduces from an isolated wheel; 113 focused tests and native risk checks pass. Share matcher/prefix encoding semantics and cover those real streaming cases. CI was not consulted; no merge or live upgrade claimed.

@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Addressed the remaining streaming-encoding finding in d57810e. The stream filter now holds protected-root prefixes using the same literal/UTF-8 percent-encoded character and case-insensitive percent-hex forms accepted by the full-path matcher, including partially received separators.

Added direct stream regressions and synthetic ACP subprocess coverage for lowercase encoded separators and a mixed %63 root byte; both answer.delta and the final answer must redact the path. Before the fix, both new direct stream cases failed by emitting a private-path prefix. After the fix, all 5 targeted stream/ACP cases and 234 tests across the four Chat/ACP suites pass. Ruff, Python compilation, mypy (19 source files), diff checks, and the pre-merge canary (2/2 selected checks) pass. Fresh hosted checks are queued and Summary is in progress; this PR remains open.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from d57810e to 378b605 Compare October 9, 2026 02:38
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Follow-up on the encoded-prefix finding: the same synthetic character-stream probe leaked uppercase, lowercase-separator, and mixed %63 roots on exact main 0e5acf, while head 378b605 emits [local-path] for all three. The five focused suites pass (117 tests); Ruff, compilation, configured mypy, diff checks, and both selected premerge checks pass. Hosted checks have restarted on this head; Summary passes and the remaining checks are pending.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from 378b605 to 7be3bbb Compare October 9, 2026 03:52
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Follow-up on current head 7be3bbb, based on main fac40bb. The rebase preserves the fix and focused tests. Validation on this head: path-redaction, response-parsing, DSH adapter, and active-turn suites: 126 passed; Ruff, Python compilation, and git diff --check passed. On the earlier exact-main reproduction at 0e5acf, character-by-character streaming exposed the encoded protected paths; intervening main commits did not touch the changed chat implementation or focused test paths. Current hosted checks: Summary passed; DCO sign-off, dependency review, and release build are queued. The prior changes-requested review is attached to an older head; this rebased head is ready for fresh review. No merge is claimed.

@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from 7be3bbb to a9a3f1c Compare October 9, 2026 04:45
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Current-head review request: PR #5978 is now at a9a3f1c, based on main b44b84f. The latest streaming-prefix update and its regression cases are included in this head. I reran tests/test_chat_path_redaction.py on a9a3f1c: 52 passed; Ruff and git diff --check b44b84f...HEAD passed. Hosted Summary, DCO, and dependency review pass; the Python changes and Release Artifacts build are still pending. The CHANGES_REQUESTED review is attached to older head 16f0be4; please refresh review against a9a3f1c when convenient.

Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
@mikamikasuki
mikamikasuki force-pushed the codex/loopx-redact-encoded-separators branch from a9a3f1c to fa74c05 Compare October 9, 2026 05:26
@mikamikasuki

Copy link
Copy Markdown
Contributor Author

Rebased the same PR branch onto current canonical main. Current PR head: fa74c05; base: 647e216. Validation on this exact head: path-redaction, response-parsing, DSH adapter, and active-turn suites — 126 passed; Ruff, Python compilation, git diff --check origin/main...HEAD, and all seven DCO trailers pass. The intervening main commits did not touch the changed Chat source or focused path tests. Hosted checks restarted on the rebased head and are pending. The existing CHANGES_REQUESTED review is attached to 16f0be4; please refresh review against fa74c05.

@BigDataDZ BigDataDZ 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.

Independent verification on a host/platform combination the prior review rounds did not cover: native Windows 10, zh-CN (cp936 ANSI code page), Python 3.12, head fa74c055 applied onto base 647e216.

Failing-before reproduces: running the head tests against the base tree fails 19 of 52 — the encoded-form gaps are real and the new tests genuinely pin them.

Passing-after on Windows: 48 of 52 pass at head, including the lowercase-separators and mixed-root-byte streaming cases that the second CHANGES_REQUESTED flagged at 16f0be43 — confirming those fixes hold outside Linux.

The 2 remaining local failures are environment blocks, not logic defects: both (test_dot_segment_encoded_alias_of_private_root_is_fully_redacted, test_acp_stdio_final_and_stream_hide_private_aliases_and_keep_public_filename) die in fixture setup with OSError: [WinError 1314] — creating the symlink alias requires administrator privileges or Developer Mode on Windows. Suggest a graceful skip (attempt os.symlink, skip on WinError 1314) so Windows contributors get a clean signal instead of two false failures.

New coverage — non-ASCII private roots (not exercised in prior rounds): a long Chinese-character private root (each character is a 3-byte UTF-8 sequence, so 9 percent-encoded chars per character) probed through both redact_local_paths and the char-by-char VisibleResponseStreamFilter, across six forms: raw unicode, uppercase/lowercase/mixed-case percent-encoding, and / vs %5C encoded separators. All six redact to [local-path] in the final answer with zero partial leakage in the streamed fragments (first probe run misconfigured the declared roots as project-only and is discarded; corrected run declares the project root plus the private root as in the existing tests).

One note for the author: with only a single declared protected path, the filter classifies a path under it as a project-relative file (./secret/gate.json) rather than a private descendant — worth a doc line that private roots are declared as a second entry alongside the project root.

This is supplementary evidence for the ongoing review loop, not a verdict on the open CHANGES_REQUESTED items.

@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: fa74c0555d258fdb2bab5f2311d31fa57bce2f52; frozen base: 647e216bacc0a69368dccc18fcb52dcba6002553.

动机

前端与飞书 Chat 用户需要从 Agent 回答中核对文件位置,同时隐藏已声明的私有目录。
旧版遇到编码的本机路径时可能原样显示;本改动希望普通项目文件保留相对位置,私有后代在逐字输出及最终回答中隐藏。
已验证旧长根编码流式反例修好;编码后的 Windows 大小写等价私有根仍原样出现在逐字输出和最终回答中。
不改变文件访问、Goal/Todo、调度或额度权限,也不关闭 #5136 的全部迁移。
编码后的 Windows 大小写别名仍可绕过私有根检测。

召回建议用于核验真实使用、恢复和有界交付;旧 recovery-RFC 结论不继承,未证明记忆带来模型效用。

改动思路

同一 Chat 展示 owner 应统一完整匹配与流式前缀的编码和 Windows 大小写规则。
本 PR 的可验边界是完整回答和每个流式片段都保留安全文件名并隐藏声明的私有后代。

不修改旧版会继续留下泄漏;新 capability 或扩大缓冲上限也不能替代语言一致性。

具体改动

完整 B..H 两文件 +377/-31:Chat +157/-20,原路径测试 +220/-11。覆盖全部 matcher、raw/decoded 归一化、project-relative、结构化字段、流式前缀和实际 ACP 回归;没有状态或安装改动。

关键代码讲解

  • _local_path_pattern(chat.py:48–83)接受字面/UTF-8 百分号根字符、编码分隔符及绝对 dot alias 候选;但根字符按原大小写生成。
  • redact_local_paths(chat.py:172–280)比较 raw/decoded 组件,私有根优先,安全文件显示相对路径;Windows 组件会 casefold,但只有 regex 先匹配成功才进入这里。
  • VisibleResponseStreamFilter._protected_root_atoms/_is_partial_protected_root(chat.py:384–421)替换有限 quote 拼写表,保留部分百分号 triplet。原大写、小写、混合字节、部分编码的长根均通过真实 ACP 字符流。
  • _run_acp_answer 和路径测试通过实际 initialize/session/new/prompt 子进程分别采集 answer.delta 与 final;不是仅让 mock 返回脱敏后的文本。

规格:spec_ref docs/guides/personal-workspace-user-guide.md;spec_revision 647e216bacc0a69368dccc18fcb52dcba6002553。冻结指南及维护者 #5136 direction 3。逐项:answer path presentation not_met(下面反例);display is not authority implemented;one shared detection with destination policies implemented(仍复用 public_safe_text,输出政策留在 Chat)。

语义与CI对齐

未新增权威状态或权限;root 匹配/流式 pending 属于现有展示边界。开发期 advisory 0 候选只说明扫描范围,全树语义和 native premerge 2/2 通过;不能据此证明路径语义正确。没有查询、轮询或等待 CI。

对主干的风险

[P1] 编码的 Windows 大小写别名仍绕过声明的私有根。 以合成 project Q:\project、private Q:\project\runtime 输入以下两项:

  • q%3A%5Cproject%5Cruntime%5Csecret.txt
  • Q%3A%5CPROJECT%5CRUNTIME%5Csecret.txt

在这个 head,直接 parser、逐字符 filter、真实 ACP 的 delta 和 final 都原样输出。对照:raw q:\PROJECT\RUNTIME\secret.txt 以及原大小写的编码根会隐藏;普通项目文件保留 ./notes/report.md。这与实现自身对 Windows 组件的 casefold 意图和已声明私有根的输出契约矛盾。编码后的冒号又不能被通用 drive matcher 兜住,因此后面的归一化根本没有机会执行。

独立固定 base/head:base 对四种长编码均泄漏,head 全部隐藏;base 的规范 Windows 编码也泄漏,head 修复,但两个大小写变体两边仍泄漏。这是本 PR 宣称覆盖的原有缺口,未称为本次新增回归,也未声称读取了文件内容。

最小修复:在现有 owner 中统一完整匹配和流式前缀的 Windows 大小写/字符编码语言,同时保留 Unix 大小写语义、私有根优先级和安全项目文件名。加入上述别名、分隔符/字符混合、chunk splits 和实际 ACP delta/final 反例;不靠扩大缓冲阈值或 final 重写。

本轮独立 152 项路径/响应/DSH/active-turn/Lark 测试、Ruff、configured mypy 19、diff/compile 和 native canary 2/2 通过。首次选错一个测试文件名、语义脚本名及未安装 npm dev dependency 的准备失败均保留,修正环境后通过;未把它们归因于 PR。Windows 证据是合成字符串经真实协议,不是原生 Windows 文件系统;live Lark、付费模型、打包渲染和长期 CPU 成本未测。

我的整体评价

REQUEST_CHANGES。旧流式反例已实际关闭,体验改善有明确证据;当前仍不能完成这项有界隐私承诺。每个后续 Turn 都可能重复披露同一类编码路径,长期效果不能由最终正常或测试数量推断。

相关未来重构检查落在同一边界:两个 path_parts/display_path_parts 的归一化可以共用一个保留显示大小写的 helper,完整/prefix matcher 共享 root 字符政策。可随当前修复一起做小范围整合,无需新 capability、广泛 TS 迁移或完成整个 #5136。

English verdict: REQUEST_CHANGES — fa74c05; original long encoded stream findings are fixed, but encoded Windows case aliases still expose declared private roots in both real ACP deltas and final answers. 152 focused tests, static checks and native canary2 pass. Fix the same matcher/prefix case language and retain safe filenames. CI was not consulted.

Comment thread loopx/chat.py
f"%{byte:02X}"
for byte in char.encode("utf-8", errors="surrogateescape")
)
encoded_chars.append(f"(?:{re.escape(char)}|(?i:{encoded}))")

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.

[P1] 编码的 Windows 大小写别名仍绕过声明的私有根。声明 Q:\project\runtime 后,q%3A%5Cproject%5Cruntime%5Csecret.txt 与 Q%3A%5CPROJECT%5CRUNTIME%5Csecret.txt 在真实 ACP 的 delta/final 都原样输出;raw 大小写别名与原大小写编码形式则会隐藏。这里的 per-character alternatives 只接受原字符大小写,编码冒号又绕过 generic drive matcher,因此后面的 Windows casefold 根本没有机会运行。请统一完整/prefix 的 Windows 字符等价及编码规则,保留 Unix case sensitivity、私有优先与安全相对文件名,并补 real ACP 正反例。冻结 base 同样泄漏,这是当前 PR 宣称覆盖但未修复的缺口,不是新增回归。

This branch has not been deployed

No deployments
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