From 5a0a3362fb1b1817890bd1570a067388e0536957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 18 Sep 2026 00:29:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20DirectProject=20=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E5=A2=9E=E9=87=8F=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对 reasoning 与 agentMessage 增量复用历史正文脱敏和截断规则 补充密钥与绝对路径不泄露的 Rust 回归测试 --- .../src/agent/codex_app_server/mod.rs | 45 +++++++++++++++++-- .../src-tauri/src/agent/direct_thread_wire.rs | 8 ++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index 4a7d9b28f..7bf8b2484 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -1124,6 +1124,16 @@ fn direct_codex_reasoning_delta_event( }) } +/// DirectProject 的流式正文在进入 Thread Manager 前就完成脱敏;前端不得接触原始增量。 +fn direct_codex_thread_delta_event( + root: &std::path::Path, + item_id: String, + kind: DirectThreadDeltaKind, + delta: &str, +) -> DirectThreadEvent { + DirectThreadEvent::item_delta(item_id, kind, direct_thread_delta_text(root, delta)) +} + /// 通知 → 回合事件的唯一分类函数:运行态读取器与单测共用这一份。 /// /// 读取器只负责"必须有 turnId 才处理"的前置条件与节流(活动 / 正文),分类不在这里之外 @@ -3105,10 +3115,11 @@ impl CodexAppServerConnection { &direct_thread_id, // 事件自足:增量自带 item 身份与正文类别(正文 / 思考), // 前端 reducer 不允许靠猜 itemId 的来源决定 kind。 - DirectThreadEvent::item_delta( + direct_codex_thread_delta_event( + history_root, item_id.clone(), DirectThreadDeltaKind::Message, - delta.clone(), + &delta, ), ); } @@ -3144,10 +3155,11 @@ impl CodexAppServerConnection { if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject { append_direct_thread_event( &direct_thread_id, - DirectThreadEvent::item_delta( + direct_codex_thread_delta_event( + history_root, item_id, DirectThreadDeltaKind::Reasoning, - delta, + &delta, ), ); } @@ -4893,6 +4905,31 @@ mod tests { )); } + #[test] + fn direct_thread_message_and_reasoning_deltas_are_sanitized_before_enqueue() { + let root = std::path::Path::new("/workspace/direct-project"); + let raw = "key=sk-abcdefghijklmnop project=/workspace/direct-project/assets/a.png private=/root/secret.txt"; + for kind in [ + DirectThreadDeltaKind::Message, + DirectThreadDeltaKind::Reasoning, + ] { + let event = direct_codex_thread_delta_event(root, "item-1".to_string(), kind, raw); + let wire = serde_json::to_string(&event).expect("serialize direct thread delta"); + assert!( + !wire.contains("sk-abcdefghijklmnop"), + "不得泄漏密钥:{wire}" + ); + assert!( + !wire.contains("/root/secret.txt"), + "不得泄漏绝对路径:{wire}" + ); + assert!( + wire.contains("assets/a.png"), + "项目内绝对路径应归一成相对路径:{wire}" + ); + } + } + /// 判据:读取器与单测共用同一个分类函数,这些分支的行为被钉在这里。 /// /// 参数:method / params / 正文候选 / 安全活动类别 / turnId。 diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs index d9374a3a0..f6b781218 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_thread_wire.rs @@ -365,6 +365,14 @@ fn item_text(root: &Path, item: &Value) -> Option { )) } +/// 流式正文与完成态条目使用同一套脱敏和字符预算,避免增量文本绕过历史投影的安全边界。 +pub(crate) fn direct_thread_delta_text(root: &Path, value: &str) -> String { + bounded( + &sanitize_detail_text(root, value), + DIRECT_THREAD_TEXT_MAX_CHARS, + ) +} + fn item_at_ms(item: &Value, observed_at_ms: u64) -> u64 { let from_metadata = item .get("internal_chat_message_metadata_passthrough")