AIGC标识 Cargo 1.100 的冷却期:1 天窗口把 35 分钟前的版本留在了外面

Rust 1.100.0 是 11 月 12 日发的。官方博客把 Cargo 的 min-publish-age 写成了稳定功能:在 .cargo/config.toml 里设一个最短发布时长,cargo update 解析依赖时就跳过比这更新的版本。我这台机器的默认工具链还是 1.88.0,用户级配置文件也不存在。我没有改默认,只用 cargo +1.100.0 在一个临时项目里把窗口从 1 小时放到 30 天,对照同一条依赖被锁到哪个版本。

跑下来,拦截本身是准的:1 天窗口把 35 分钟前发布的 coneko-term 0.1.12 留在外面,锁文件停在 39 小时前的 0.1.7。但博客里的三处说法和实际输出对不上,1.88.0 对这个新配置键既不报错也不生效。

先说结论

怎么跑 结果
1.100.0,global-min-publish-age = "1 day",没有锁文件 锁到 0.1.7,输出写明 0.1.12 是 35 分钟前发布的
同一份配置,CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow 锁到 0.1.12,并注明 minimum age 1 day
1.88.0,同一份配置 退出码 0,不报错,直接锁到 0.1.12

三条可以带走的:

  1. 这个功能默认关着。文档写的默认值是 "0",不写配置时 1.100.0 和 1.88.0 一样,拿到的是最新版。
  2. 博客示例里那行 available: 只在「这次解析新锁了一个包」时出现。包已经在锁文件里时,要加 --verbose 才看得到。
  3. 用环境变量放进来的新版本不会一直留着。再跑一次普通的 cargo update,它被降回窗口内的旧版本。

窗口和锁到的版本

背景

Rust 1.100.0 的发布公告(2026-11-12,Rust Release Team)把这件事放在「Cargo min-publish-age a.k.a. dependency cooldowns」一节。理由是恶意版本经常在发布后几小时到几天内被扫描器或社区发现,所以解析依赖时可以先等过这个窗口。博客给的配置是:

[registry]
global-min-publish-age = "3 days"

博客示例的输出是 Locking 2 packages to highest compatible version as of min-publish-age,下面两行分别是 Adding cc v1.11.0 (available: v1.18.0, published 3 hours ago) 和 Adding clap 4.6.7 (available: 4.6.9, published 2 days ago)。放行的例子是:

CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo update clap --precise 4.6.9

博客接着说:The newer version will be recorded in Cargo.lock and kept from then on until a later cargo update re-resolves it。还提醒了三件事:索引里没有 pubtime 字段的版本不会被拦住;cargo install 对要装的包和它的依赖都不看这个时长;冷却期只降低一部分供应链风险,不能单独当安全措施。

键的定义在 Cargo 配置文档。registry.global-min-publish-age 类型是字符串,默认 "0",对应环境变量 CARGO_REGISTRY_GLOBAL_MIN_PUBLISH_AGE,文档标注 MSRV: Respected as of 1.100+。合法的值是整数加 seconds、minutes、hours、days、weeks、months 之一,或者 "0"。另一个键 resolver.incompatible-publish-age 默认 deny,也就是「太新的版本直接不参与解析,除非它已经在锁文件里」;设成 allow 等于关掉检查。这个键的环境变量就是博客里那个 CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE。

这个设计出自 RFC 3923(2026-02-23 提出,跟踪 issue 是 rust-lang/cargo#17009)。RFC 把动机写成两件:等自动化扫描先跑一轮,以及让愿意当金丝雀的人先吃新版本。RFC 自己也写了,这不是完整的防护。

实测

环境是 Windows 10。测之前:

rustc 1.88.0 (6b00bc388 2025-06-23)
cargo 1.88.0 (873a06493 2025-05-10)

rustup show 显示默认工具链是 1.88.0-x86_64-pc-windows-msvc,~/.cargo/config.toml 不存在。1.100.0 这条工具链当时已经装在 rustup 里,我没有执行 rustup default,测完默认仍是 1.88.0。用 + 指定之后:

cargo 1.100.0 (3acc79c0c 2026-11-05)
rustc 1.100.0 (0ef1bc386 2026-11-09)

临时项目叫 ageprobe,只有一个依赖 coneko-term = "0.1"。选它是因为写作当天 crates.io 上的 0.1.12 发布于 2026-11-13 10:17:02 UTC,我跑下面这条命令时它才发布 35 分钟,而 0.1.2 到 0.1.12 这 11 个版本的发布时间又正好落在 1 小时到 7 天之间,一个窗口能切出好几档。下载 0.1.12 的 .crate 看过:没有 build script,依赖只有 unicode-width 0.2,src/lib.rs 是终端解析的库代码。配置写在项目的 .cargo/config.toml:

[registry]
global-min-publish-age = "1 day"

删掉 Cargo.lock 再跑:

$ cargo +1.100.0 update
    Updating crates.io index
     Locking 2 packages to highest Rust 1.100.0 compatible versions as of 24 hours ago
      Adding coneko-term v0.1.7 (available: v0.1.12, published 35 minutes ago)

锁文件里 coneko-term 是 0.1.7。这条命令跑完的时刻是 2026-11-13 10:52 UTC。crates.io 稀疏索引里 0.1.7 的 pubtime 是 2026-11-11 19:12:50Z,距此 39 小时 39 分,落在 1 天窗口内;0.1.8 的 pubtime 是 2026-11-12 15:58:55Z,距此 18 小时 53 分,被排除了。

同一份锁文件再跑一次,那行 available: 消失了,只剩一句提示:

$ cargo +1.100.0 update
    Updating crates.io index
     Locking 0 packages to highest Rust 1.100.0 compatible versions as of 24 hours ago
note: pass `--verbose` to see 1 unchanged dependencies behind latest

$ cargo +1.100.0 update --verbose
    Updating crates.io index
     Locking 0 packages to highest Rust 1.100.0 compatible versions as of 24 hours ago
   Unchanged coneko-term v0.1.7 (available: v0.1.12, published 36 minutes ago)

放行用博客给的环境变量。已有锁文件时它会升级,没有锁文件时直接拿到最新:

$ CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo +1.100.0 update
    Updating crates.io index
     Locking 1 package to highest Rust 1.100.0 compatible version
    Updating coneko-term v0.1.7 -> v0.1.12 (published 25 minutes ago, minimum age 1 day)

不加这个环境变量,直接指定版本会被拒,退出码 101:

$ cargo +1.100.0 update -p coneko-term --precise 0.1.12
    Updating crates.io index
error: failed to select a version for the requirement `coneko-term = "^0.1"`
  version 0.1.12 is too new (published 25 minutes ago, minimum age 1 day)
location searched: crates.io index
required by package `ageprobe v0.1.0 (...)`
help: to use too-new packages anyways, re-resolve with `CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow`

把窗口拉到比所有版本都长,报错会把每个被拒的版本连同它的年龄逐条列出,同样是退出码 101,而且不生成 Cargo.lock:

$ cargo +1.100.0 update
    Updating crates.io index
error: failed to select a version for the requirement `coneko-term = "^0.1"`
  version 0.1.2 is too new (published 7 days ago, minimum age 30 days)
  ...
  version 0.1.12 is too new (published 28 minutes ago, minimum age 30 days)

最后用 1.88.0 对同一份 "1 day" 配置跑:

$ cargo +1.88.0 update
    Updating crates.io index
     Locking 2 packages to latest Rust 1.88.0 compatible versions

退出码 0,没有任何 warning,锁文件里是 0.1.12。

和博客对不上的三处

第一处,示例输出的措辞。 博客写的是 to highest compatible version as of min-publish-age。1.100.0 的实际输出把 Rust 版本和具体时长都写了出来:to highest Rust 1.100.0 compatible versions as of 24 hours ago。只有把值设成 "0" 时才退回博客那种说法,输出变成 as of min-publish-age,因为 0 没有时长可显示。

第二处,available: 那行不是每次都有。 博客的例子是一次 cargo update 就打出两行 Adding … (available: …)。实测里这行只在 Cargo 本次真的往锁文件里加包或改包时出现。锁文件已经定好、再跑一次时,默认输出只有一行 note,被拦住的版本要 --verbose 才打印,前缀也从 Adding 变成 Unchanged。

第三处,放进来的新版本留不久。 博客说新版本会记进 Cargo.lock,一直留到以后某次 cargo update 重新解析。我按它给的方式放行:

$ CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo +1.100.0 update -p coneko-term --precise 0.1.12
    Updating crates.io index
    Updating coneko-term v0.1.7 -> v0.1.12 (published 29 minutes ago, minimum age 1 day)

$ cargo +1.100.0 update
    Updating crates.io index
     Locking 1 package to highest Rust 1.100.0 compatible version as of 24 hours ago
 Downgrading coneko-term v0.1.12 -> v0.1.7 (available: v0.1.12, published 29 minutes ago)

下一次不带环境变量的 cargo update 就把它降回去了,不是留到某次更晚的解析。Cargo 1.100.0 源码(提交 3acc79c0c,tag 0.101.0)里的测试 locked_versions_preserved 保的是另一件事:锁文件里已经有太新的版本时,cargo check 不去动它。我验证了这条,cargo check 确实保持 0.1.12 不动。所以「留在锁文件里」只对不重新解析的命令成立,cargo update 本身每次都重新解析。

窗口切在哪,靠索引里的 pubtime

crates.io 稀疏索引 https://index.crates.io/co/ne/coneko-term 里每个版本都有 pubtime,和 crates.io API 的 created_at 一致。下表的年龄按 2026-11-13 10:52 UTC 回算,就是上面那条 cargo update 跑完的时刻:

版本 pubtime(UTC) 当时的年龄
0.1.12 2026-11-13 10:17:02 35 分钟
0.1.11 2026-11-13 02:49:25 8 小时 2 分
0.1.10 2026-11-12 18:29:56 16 小时 22 分
0.1.9 2026-11-12 16:15:10 18 小时 37 分
0.1.8 2026-11-12 15:58:55 18 小时 53 分
0.1.7 2026-11-11 19:12:50 39 小时 39 分

用这些年龄去对实际锁到的版本,窗口是按真实时长切的,不是按整天取整。"1 hour" 锁到 0.1.11,因为 0.1.12 只有 35 分钟;"7 hours" 仍是 0.1.11,"8 hours" 就退到 0.1.10,和 0.1.11 当时 8 小时出头的年龄吻合。"18 hours" 锁到 0.1.9(18 小时 37 分前),"20 hours" 退到 0.1.7,因为 0.1.8 是 18 小时 53 分前,比 20 小时新。这几档是在 10:43 到 10:44 UTC 之间跑的,比上表早约 8 分钟,每档的年龄要减去这 8 分钟,分界不变。

源码里的解析在 src/util/time_span.rs:按秒换算,1 个月按 2,629,746 秒,也就是 30.436875 天的平均值,不是自然月。数字和单位之间的空格可有可无,"1 day"、"1day"、"1 days" 三次都输出 as of 24 hours ago 并锁到 0.1.7。写错才报错:

error: invalid value for `registry.global-min-publish-age`: expected a value of the form "N seconds/minutes/hours/days/weeks/months", got: "yesterday"

两个边界也试了。不写这个键,输出退回 Locking 2 packages to highest Rust 1.100.0 compatible versions,锁到 0.1.12。写成 "0",锁到的还是 0.1.12,只是文案变成 as of min-publish-age。

配置放哪都能生效。删掉项目里的 .cargo/config.toml,改到 CARGO_HOME(这台机器是 F:/dev/cargo,不是 ~/.cargo)的 config.toml 写同样的 "1 day",输出和锁到的版本一模一样。测完我把这个文件删了,确认用户级配置恢复成不存在。

cargo install 确实不看

博客说 cargo install 对要装的包和它的依赖都忽略最短时长。我用 allup 验证了前半句:1.0.18 的 pubtime 是 2026-11-13 10:13:53Z,我跑这条命令时它发布了 31 分钟,而上一版 1.0.17 发布于 2026-08-13,远在 1 天之外。

$ cargo +1.100.0 install allup --root <临时目录> --config 'registry.global-min-publish-age="1 day"'
    Updating crates.io index
  Installing allup v1.0.18
   Compiling allup v1.0.18
    Finished `release` profile [optimized] target(s) in 28.21s
   Installed package `allup v1.0.18` (executable `allup.exe`)

全程没有 published、minimum age 或 available: 这些字样,装上的就是发布了 31 分钟的 1.0.18。装到独立的 --root,没有碰 PATH 里的任何已装包。

依赖那半句没有在这个包上观察到:allup 1.0.18 的 11 个依赖里最新的 reqwest 0.13.5 发布于 2026-09-08,都比 1 天老,所以这次只能证明「要装的包本身不受限」。Cargo 自己的测试 cargo_install_allows_too_new_deps 覆盖的是依赖那半句。

暂时做不了怎么办

1.88.0 不会拦你,也不会提醒你。团队里只要有人还在 1.100 之前的 Cargo 上跑 cargo update,同一份配置挡不住他,他生成的锁文件会带着窗口内不该出现的版本进来。想先用起来,至少让负责更新锁文件的那台机器到 1.100。

窗口设得比所有可用版本都长时,解析直接失败,不会自动退到更老的版本。coneko-term 的全部 11 个版本都在 7 天以内,30 天的窗口让 cargo update 和 cargo check 都做不成,只能临时用那个 allow 环境变量,或者把窗口改短。

cargo install 不受保护。装命令行工具要自己钉版本,cargo install allup@1.0.17 这种,不能靠这份配置。

另外,"1 day" 挡的是 24 小时内的版本,挡不住更早埋进去、现在才被依赖到的版本。博客和 RFC 都把话说在前面了:这只是降低一部分风险。

清单

参考

posted @ 2026-11-13 18:57  SpectrAI光谱AI  阅读(13)  评论(0)    收藏  举报