test(presenter): M103 r2 —— 出站脱敏补守卫,另三处站点与 Bearer 右边界

P1(守卫缺失,产品代码是对的):
- 新增 RecordingTransport + outbound_prompt_is_scrubbed_before_it_reaches_the_backend:
  断言真正发给后端的那份 prompt 里没有 JWT(rail 侧也脱敏,「界面干净」证明不了出站安全)
- 变异:删掉 agent_submit 的出站 scrub ⇒ 该测试红(输出见完成报告)

另三处同类站点(r2 P2-1):
- confirm_tool_name_is_scrubbed(ConfirmRequest.tool)
- tool_message_from_the_backend_has_its_tool_view_scrubbed(MessageAppended 的 ToolCallView 分支)
- outbound_error_text_is_scrubbed(Err 文案,provider 401 常回显 key)
- 三条各自变异后都变红

其他:
- bearer_right_boundary_is_pinned(tests.rs):钉住 mask_bearer 的停字符集,收窄成 [' '] 即红
- 文档订正:rail.rs 模块 §2 改成「两道闸门(出站/显示)各守一侧」+ 与 agent 侧的实际关系
  (名单逐字相同、path 语义相同、mask_bearer 是行为不完全等价的自写迁移;重复实现是欠账,
  不读成「已无差异」也不读成「已经漂移」)
- agent_set_draft 与 rail_text 各写明「draft 不在断言覆盖范围」及原因
- tests_red_line.rs 4 行 405 → 380;各文件均 ≤400
This commit is contained in:
AskaEth committed 2026-11-04 00:21:19 +08:00
1 parent 06461df91b
commit a2b1a06901
4 files changed
+222 -17

No files matched your search

+28
View File
@@ -7,6 +7,7 @@
use super::mock::{MockAgentTransport, Script};
use super::{AgentTransport, ConfirmVerdict, Presenter};
use crate::agent::rail::scrub_text;
use crate::agent::AgentRail;
use crate::dto::{AgentEvent, AppEvent, ChatMessage, ChatRole, ConfirmRequest, Sensitivity};
@@ -233,6 +234,33 @@ fn tool_call_finished_without_a_pending_call_never_rewrites_history() {
assert!(tool.ok);
}
// ────────────────── scrub_text 的 Bearer 右边界 ──────────────────
/// 钉住 `mask_bearer` 的**右边界**(r2 P2-2:停字符集收窄或换字符都必须让这条红)。
///
/// 方向说明:停字符越少 = 吞得越多(安全);放宽才会裸奔。所以句点故意不在停字符集里
/// (吞掉句点,换「短 JWT 只掩前半段」是不划算的)。
#[test]
fn bearer_right_boundary_is_pinned() {
// 空白 / 逗号 / 分号 / 引号是停字符:令牌之后的内容原样保留
assert_eq!(
scrub_text("Authorization: Bearer abc123, retry=1"),
"Authorization: Bearer <redacted:token>, retry=1"
);
assert_eq!(
scrub_text("Bearer abc123; rest"),
"Bearer <redacted:token>; rest"
);
assert_eq!(
scrub_text("\"Bearer abc123\""),
"\"Bearer <redacted:token>\""
);
assert_eq!(scrub_text("Bearer abc123"), "Bearer <redacted:token>");
// 句点不是停字符:整段吞掉(宁可多掩,也不让短 JWT 只掩前半段)
assert_eq!(scrub_text("Bearer abc123."), "Bearer <redacted:token>");
assert_eq!(scrub_text("Bearer eyJ.abc.def"), "Bearer <redacted:token>");
}
// ─────────────────────── 提交前置条件 ───────────────────────
#[test]