From a07482f828c358c0491fabaea2a0a8c9d7d5162c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 11 Nov 2026 10:07:11 +0800 Subject: [PATCH] =?UTF-8?q?docs(core):=20M240=20r2=20=E5=A4=8D=E5=AE=A1=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E4=B8=A4=E5=A4=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=EF=BC=88`=3D`=20=E8=BE=B9=E7=95=8C=E4=BB=A3?= =?UTF-8?q?=E4=BB=B7=E5=86=99=E6=B8=85=E3=80=81DNS=20=E5=BD=A2=E5=BD=92?= =?UTF-8?q?=E5=B1=9E=E6=94=B9=E6=AD=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 纯注释,判据/阈值/测试断言/扫描器零改动。 ① `is_dotted_token_char` 的 doc 原写「JWT 自己的 `=` 只可能是末尾填充,拆出去不损伤 覆盖面」——作为无条件说法不成立。改成有范围的措辞,并把这条例外并入模块文档 「旧行为抹、新行为留」第 1 条(不新开章节):`-DaccessToken=abcdefgh.ijklmnop.qrstuvwx` (整段 40 字符、三段各 ≥8,旧规则抹)现在 `=` 切段后值只剩 26 < 40 → 原样留下。 旧/新两侧都实测过(`git show 74be510` + rustc 探针 / 当前代码探针)。 ② `cdn_and_mirror_urls_survive` 里 `resources.download.minecraft.example-cdn` 的注释 原写「全靠必须含大写这条必要条件挡住」——不成立:它是 DNS 形状,由 `is_url_authority` → `is_dns_host` 豁免(删掉大写判据该测试仍绿)。改成正确归属,与 `lowercase_dotted_names_are_spared` / `the_uppercase_requirement_is_load_bearing` 的 doc 口径对齐(DNS 形靠主机名豁免、非 DNS 形靠必须含大写)。 --- crates/core/src/launch/redact/shape.rs | 12 +++++++++++- crates/core/src/launch/redact/tests.rs | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/core/src/launch/redact/shape.rs b/crates/core/src/launch/redact/shape.rs index 1f40f05..b8eacd0 100644 --- a/crates/core/src/launch/redact/shape.rs +++ b/crates/core/src/launch/redact/shape.rs @@ -31,6 +31,10 @@ //! //! 1. **`=` 边界**:`-Dminecraft.launcher.brand=UnluckyLauncher` —— 旧行为把 `键=值` 粘成 //! 一段,键里的点号凑够段数/总长;现在两半各自不达标。(这就是要修的品牌串误杀。) +//! 同一处边界的**代价**:`-DaccessToken=abcdefgh.ijklmnop.qrstuvwx`(整段 40 字符、 +//! 三段各 ≥8,旧规则抹)现在 `=` 切段后值只剩 26 < 40 → 原样留下。也就是说 +//! **紧跟 `=`、自己又不够 40 的带点值**不再被形状规则兜住,只剩"登记明文值"那条路径, +//! 这是换品牌串保留付出的代价。 //! 2. **填充不算段长**:`eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.c2lnbm==` —— //! 旧行为把 `==` 算进末段(8 ≥ 8),现在 `=` 在段外、末段实际 6 < 8,整串留下。 //! 真 JWT 的签名段是 43+ 字符(HS256)/ 342(RS256),6–7 字符的末段只可能是被裁剪或 @@ -55,7 +59,13 @@ pub(super) fn is_opaque_char(ch: char) -> bool { /// /// `=` 是**赋值边界**而不是 token 本体。把它留在字符集里,`-D=<值>` 就会被粘成一个 /// run:键里的点号(`minecraft.launcher.brand`)会把值一起拖进段数统计,M239 实测的品牌串 -/// 误杀就是从这儿来的。JWT 自己的 `=` 只可能是末尾填充,拆出去不损伤覆盖面。 +/// 误杀就是从这儿来的。 +/// +/// 这条边界的代价**有范围**,别读成"不损伤覆盖面":`=` 切段之后,**紧跟 `=`、自己又不够 +/// 40 字符**的带点值不再被形状规则兜住——`-DaccessToken=abcdefgh.ijklmnop.qrstuvwx`(整段 +/// 40 字符、三段各 ≥8,旧规则抹)现在切出 26 < 40 而原样留下。这是换品牌串保留的代价, +/// 记在模块文档「旧行为抹、新行为留」第 1 条的末句;真 token 仍由 [`super::Redactor::with_secret`] +/// 那条明文值替换路径扛住。 pub(super) fn is_dotted_token_char(ch: char) -> bool { ch != '=' && (is_opaque_char(ch) || ch == '.') } diff --git a/crates/core/src/launch/redact/tests.rs b/crates/core/src/launch/redact/tests.rs index b68ac94..a75382a 100644 --- a/crates/core/src/launch/redact/tests.rs +++ b/crates/core/src/launch/redact/tests.rs @@ -135,7 +135,7 @@ fn hmcl_shaped_argv_survives_verbatim() { assert_eq!(scrub(argv), argv); } -/// M239 的 U1–U6:CDN / 镜像主机名(DNS 授权段 + 小写必要条件)。 +/// M239 的 U1–U6:CDN / 镜像主机名(判据 = 首段是 DNS 主机名 → 只判路径那半)。 #[test] fn cdn_and_mirror_urls_survive() { for text in [ @@ -145,7 +145,8 @@ fn cdn_and_mirror_urls_survive() { "https://libraries.minecraft.net/net/minecraft/launchwrapper/1.12/launchwrapper-1.12.jar", "https://api.minecraftservices.com/entitlements/mcstore", "https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.2.0/forge.jar", - // 没有点号分段的最长主机名形状(全靠"必须含大写"这条必要条件挡住) + // 40 字符的纯主机名形状:DNS 形 → 靠首段主机名豁免(不是靠大写判据, + // 大写判据只管非 DNS 形状,归属见 `lowercase_dotted_names_are_spared` 的 doc) "resources.download.minecraft.example-cdn", // 短域名 + 长路径(内容全是词形名字) "https://short.host/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",