fix(download): M295 r1 —— 代理口令脱敏、回落可见、socks5 与错因有话说
复审 r1(p2-2items / fix-then-merge)两条 p2 都是本轮新开的「用户被骗」面,三条 p3 一并收。 p2-1(凭据泄漏面):`proxy_url` 允许 userinfo,而这个键在本仓就是凭据(schema 是 `AiPolicy::sensitive()`、`config get` 打 `<redacted>`、`config_secret_test.rs` 专门守它)。第一版把 manual 的原串整条写进 `summary()`(→ CLI stderr → CI 日志 / 截图)与解析失败文案,**不需要用户犯错**。 - `redact_proxy_url` 是唯一实现:`user:password@` → `user:***@`(用户名保留、口令打掉;只有用户名 没有口令 ⇒ 原样;路径/查询里的 `@` `:` 一律不碰,也不做任何 URL 归一)。 - 四个出口全过它:`describe()`(`summary()`/stderr)、`apply()` 的错误文案(`proxy_problem()`/ GUI 提示)、手写的 `Debug`(配置与引擎被打进日志时)、`problem()`。 - 刻意例外(写进 docs §5.3):设置页那个输入框仍显示文件原值——它是编辑面,回填掩码会在用户点 「应用」时把真口令冲掉。 p2-2(GUI 对「非法地址 ⇒ 回落直连」完全静默):`proxy_problem()` 的消费者只有 CLI。 - `ProxySetting::problem()` 是新增的**纯判定**(与 `apply` 共用 `manual_proxy`):宿主用它给提示, 不必造 client。两个宿主都调它,优先级写清:GUI 常驻(设置页「HTTP 代理」行第二段 `\n已回落到直连:<原因>`)、CLI 每次装配引擎一行 stderr。 - 取舍写进 docs §5.3:回落直连(而不是拒绝下载)不挡用户,代价是「以为走代理」,所以回落**必须** 可见;fail-closed 是产品决定,不在本轮。 p3-1:`socks5://` 在 `Proxy::all` 里返回 `Ok`,但本构建没开 socks feature ⇒ 以前静默变成「所有候选 源都失败了」。现在 `manual_proxy` 先自己 `Url::parse` 拿 scheme 做白名单,不支持的直接有界错误 (「本构建不支持 socks5 代理(只支持 http / https)」)⇒ 走已有的回落 + 告知路径。 p3-3:错因不再只有 `builder error`:`reason()` 沿 `source()` 链拼一句;宽口径只补到「少写 scheme」 为止(已带 `://` 不再套一层 `http://`,否则真错因会被掩盖成「解析成功」)。实测文案: `代理地址无法解析(http://127.0.0.1:99999):invalid port number`。 p3-2:两个测试文件已在 scope 里登记,本轮未动。 判据(都落在最终字符串 / 真正可见面上): - `a_password_in_the_proxy_url_never_reaches_a_user_visible_string`:生效值行 / 错误文案 / `Debug` 四处都不含口令子串且保留 `user:***@`;`a_proxy_url_without_a_password_is_left_byte_identical` 守「没口令的地址逐字不变」。 - `a_broken_proxy_url_is_shown_as_falling_back_to_direct`(presenter):设置页那一行含 `\n已回落到直连:`、带(脱敏后的)地址、不含口令,且与 `engine.proxy_problem()` 是同一串; 合规地址没有这条提示。 - `the_console_line_never_carries_the_proxy_password`(CLI):`announce` 那一行与回落提示都不含口令。 - `unsupported_schemes_and_bad_ports_explain_themselves`(core)+ `an_unsupported_proxy_scheme_is_ refused_with_a_readable_reason`(engine)。 - 变异 M23-M26 记在 docs §6(redact 返回原文 ⇒ 3 条红;删 `proxy_note` ⇒ GUI 提示红;白名单写 `if false` ⇒ socks 红;错因换回 `builder error` ⇒ 红)。 - 端到端(真二进制 `ull install 1.20.1 --dry-run`,`CARGO_TARGET_DIR` 同前):口令样本 `PWMUSTNOTLEAK` 在 stdout+stderr 里 **0 次**,两行都是 `user:***@`;socks5 那档打出 「本构建不支持 socks5 代理(只支持 http / https)」。 - 门禁:四 crate `--all-targets` check / 测试(3206 passed, 0 failed, 45 binaries)/ `clippy --all-targets -- -D warnings` / `fmt --all --check`,另 `check --workspace --all-targets`。
This commit is contained in:
1 parent
d584a2c42e
commit
c182d8d5c9
7 files changed
+584
-167
No files matched your search
@@ -76,3 +76,35 @@ fn the_proxy_settings_reach_the_cli_engine_and_the_console_line() {
|
||||
assert!(schema.spec(key).is_some(), "{key} 没登记");
|
||||
}
|
||||
}
|
||||
|
||||
/// **口令不进终端**(r1 复审 p2-1 的 CLI 侧判据):`announce` 那一行就是用户(和 CI 日志)看到的
|
||||
/// 字符串,所以断言落在**最终的字符串**上,而不是「函数存在」。
|
||||
///
|
||||
/// 变异:`ProxySetting::describe` 改回 `format!("manual({url})")` ⇒ 第一段红(`contains(SECRET)`)。
|
||||
#[test]
|
||||
fn the_console_line_never_carries_the_proxy_password() {
|
||||
const SECRET: &str = "PWMUSTNOTLEAK";
|
||||
let dir = TempDir::new("proxy-secret");
|
||||
let ctx = ctx_at(&dir);
|
||||
write_launcher_config(
|
||||
&ctx,
|
||||
json!({ "download": { "proxy_mode": "manual", "proxy_url": format!("http://user:{SECRET}@127.0.0.1:7890") } }),
|
||||
);
|
||||
let line =
|
||||
crate::download::announce(&crate::download::tuning(&ctx)).expect("改过设置 ⇒ 该有一行");
|
||||
assert!(!line.contains(SECRET), "终端那行泄漏了代理口令:{line}");
|
||||
assert!(line.contains("user:***@"), "{line}");
|
||||
|
||||
// 非法地址那一行(回落提示)同样不许带口令。
|
||||
write_launcher_config(
|
||||
&ctx,
|
||||
json!({ "download": { "proxy_mode": "manual", "proxy_url": format!("http://user:{SECRET}@127.0.0.1:99999") } }),
|
||||
);
|
||||
let engine = crate::commands::install::build_engine(&ctx);
|
||||
let problem = engine.proxy_problem().expect("端口越界要留下原因");
|
||||
assert!(
|
||||
!problem.contains(SECRET),
|
||||
"回落提示泄漏了代理口令:{problem}"
|
||||
);
|
||||
assert!(problem.contains("user:***@"), "{problem}");
|
||||
}
|
||||
Reference in new issue
Block a user