-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Cargo RFC for min publish age #3923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
3c3e079
7fdd325
66c9b27
45e41e6
d78605c
f14ccfa
1dfe133
4ac399a
6864124
5733af0
cea13e1
8337118
51e0a7c
53f6f4b
1f05184
391cd7f
bcf027b
2a5150a
22c6790
b3ddfe8
9bb3f3f
8ee33ca
f24682c
0ef9cc2
2df26d7
0689bbf
8e9a134
6380769
52f9088
8d2b13c
63cc024
ff63c6e
b3ab608
0025ba2
eed6975
c730fea
dfa39eb
f60db7d
085ed23
b3e4bb6
19878da
8d07c75
3a1d23c
c466280
670284b
29b5772
90e3c7c
d68798b
c875967
cc4f7e0
7976883
52940eb
eadef8b
c6dda11
64f02f5
a32ca28
5bb52db
3270483
5a47fdf
6b0ce8c
bcbcb75
1db9c74
3696956
8021085
7311b7b
fcbe967
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This revision (6b0ce8c) switched to |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,327 @@ | ||||||||||||
| - Feature Name: cargo_min_publish_age | ||||||||||||
|
clarfonthey marked this conversation as resolved.
|
||||||||||||
| - Start Date: 2026-02-23 | ||||||||||||
| - RFC PR: [rust-lang/rfcs#3923](https://github.com/rust-lang/rfcs/pull/3923) | ||||||||||||
| - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||||||||||||
|
|
||||||||||||
| ## Summary | ||||||||||||
| [summary]: #summary | ||||||||||||
|
|
||||||||||||
| This proposal adds a new configuration option to cargo that specifies a minimum age for package | ||||||||||||
| updates. When adding or updating a dependency, cargo won't use a version of that crate that | ||||||||||||
| is newer than the minimum age, unless no possible version is older. | ||||||||||||
|
tmccombs marked this conversation as resolved.
Outdated
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| ## Motivation | ||||||||||||
| [motivation]: #motivation | ||||||||||||
|
|
||||||||||||
| There are a couple of reasons why one may wish not to use the most recent version of a package. | ||||||||||||
|
|
||||||||||||
| One such reason is to mitigate the risk of [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack). Often, supply chain | ||||||||||||
| attacks are found fairly quickly after they are deployed. Thus, if the dependency isn't updated | ||||||||||||
| immediately after a release, you can have some protection against a new release of a dependency | ||||||||||||
| containing malicious code. In light of recent supply chain attacks on the NPM ecosystem, | ||||||||||||
| there has been an increased interest in using automated tools to ensure that packages used | ||||||||||||
| are older than some age. This creates a window of time between when a dependency is compromised | ||||||||||||
| and when that release is used by your project. See for example the blog post | ||||||||||||
| "[We should all be using dependency cooldowns](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns)". | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving discussion from longer/less coherent thread: https://github.com/rust-lang/rfcs/pull/3923/changes#r2841703101 Rewording the arguments, I don't really think that this constitutes an empirical basis for the benefit of these delays. What would constitute a valid empirical basis is vulnerabilities that have been caught by this system after it's been implemented in other places. These should exist in practice since deployment of these mitigations is slow at best, and there should be cases where some people have the mitigations and some don't, and are thus vulnerable. But I haven't seen any so far. The only argument listed is that the time between publication and mitigation is short, and so, if we delay deployment until a certain time after publication, then deployment won't happen before mitigation. This really doesn't feel valid for one primary reason: people need to notice that there's a problem in order to mitigate it, and delaying mitigation also delays the time that people actually encounter an issue. Like, my argument is that the vulnerabilities we should be worrying about are ones like the There are plenty of notable crates where an exploit would easily be noticed immediately and delaying dependency updates would catch them, in most cases. For example, if someone tried to release a malicious version of However, there are plenty of other situations where a vulnerability would not be noticed, because Rust's trait system lets you hide code in the most unsuspecting of places. Would you notice if one crate you used suddenly started relying on a particular hasher implementation that was promising but not as scrutinized, and then that hasher implementation was running malicious code? Would you notice if a reasonable-looking trait import also happened to silently change the behaviour of a method that looked fine? The Simply assuming that a timer starts ticking from release until someone notices a vulnerability is very naïve, because while that is definitely the case for obvious exploits like adding There are definitely quick "hit-and-run" vulnerabilities that involve a compromised account that might be stopped by this, but so far we haven't seen any of those in the Rust ecosystem, and it seems like there are plenty of other low-hanging fruit with dependency updating that could be chosen instead of a simple time limit, especially considering how it can make security updates challenging to deploy. There are a few other things discussed in other threads as ways of further improving the dependency-updating and dependency-locking systems, but here I wanted to specifically hone in on why I don't think that the data here creates a valid argument in this context.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this. As we said in the other thread, this isn't meant to be an exhaustive solution but one part of improving the whole and one we can deploy rather cheaply / quick for a quick improvement while we continue to work towards the larger improvements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
FWIW, the core thesis is that the relevant parties here are security scanners, not early victims. In the Python ecosystem, for example, the overwhelming majority of malicious package reports comes from automated static analysis, not from users who have already been victimized. (And therefore it's not that people need to notice a problem, it's that systems need to be in place to monitor for problems and people should generally wait for that monitoring rather than acting as ecosystem canaries themselves.)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was not clear from the article, at least, although I definitely was biased against the article despite the fact that you could not have more clearly stated that the problem itself is overhyped. That definitely changes the situation in a way that should be reflected more clearly in the RFC IMHO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm open to suggestions on how to phrase or edit the blog post, I thought I had covered that with this paragraph:
(But I freely admit that the post is terse, and I'm always open to feedback on clarifying my language.)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's also worth pointing out that many of these attacks have involved a developer's credentials to the repository and/or package registry getting compromised, and in such cases, the developer often eventually realizes their credentials have been compromised. But that isn't always immediate, and it can take time for them to realize what happened, restore their account, and yank the malicious release.
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| Another reason to wish to delay using a new release, is because new versions can introduce new bugs. By only | ||||||||||||
| using versions that have had some time to "mature", you can mitigate the risk of encountering those bugs a little. | ||||||||||||
| Hopefully the bugs will be found before you start using the new version and thus could update to a version that fixes those bugs, | ||||||||||||
| or lock your version, so you don't get the buggy version, if the bugs apply to you. | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| Although cargo, of course, allows you to specify exact versions in `Cargo.toml` and has a lock file that can freeze the versions used, | ||||||||||||
| that requires manually inspecting each version of each transitive dependency to confirm it complies with a policy of | ||||||||||||
| using a version older than some age. | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| As such, it would be useful to have an option to put a limit on commands like `cargo add` and `cargo update` | ||||||||||||
| so that they can only use package releases that are older than some threshold. | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| ## Guide-level explanation | ||||||||||||
|
epage marked this conversation as resolved.
|
||||||||||||
| [guide-level-explanation]: #guide-level-explanation | ||||||||||||
|
|
||||||||||||
| The `registry.global-min-publish-age` [configuration option][1][^1] for Cargo can be used to specify a minimum age for published versions to use. | ||||||||||||
|
|
||||||||||||
| When set, it contains a duration specified as an integer followed by a unit of "seconds", "minutes", "days", "weeks", or "months". | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
| If a new crate would be added with a command such as `cargo add` or `cargo update`, it will use a version with a publish | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. “If a new crate would be added” is confusing. Taken colloquially, it sounds like min-publish-age would only apply to new packages (i.e.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wording was removed in 0689bbf |
||||||||||||
| time ("pubtime") before that is older than that duration, if possible. | ||||||||||||
|
tmccombs marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| The `resolver.incompatible-publish-age` configuration can also be used to control how `cargo` handles versions whose | ||||||||||||
| publish time is newer than the min-publish-age. By default, it will try to use an older version, unless none is available | ||||||||||||
| that also complies with the specified version constraint, or the `rust-version`. However by setting this to "allow" | ||||||||||||
| it is possible to disable the min-publish-age checking. | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to understand when I would want to set this. Is there perhaps an example we can provide of typical use-cases? Isn't setting this to allow the equivalent of removing the registry.global-min-publish-age configuration? Or does registry.global-min-publish-age apply always (e.g., even with a lock file?) whereas resolver.incompatible-publish-age only applies when we're actively searching for new versions?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the question "why set to allow when you can set the time to 0?" It would be good to clarify this. One controls time, the other behavior but both have a way to turn it off due td their design, I'd say turning off the behavior is for tranisently turning it off for all registries rather than setting it for each registry.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the common case for most users is that they only have one registry, right? In that case just commenting out or setting the time to zero feels better to me than complicating the configuration out of the gate with two options that interact. (It seems like we can always add the extra configuration to temporarily opt out later).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added this to Unresolved Questions, though I think Mark has a point.
I am slightly towards removing
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link to commit? For myself, I'm unconvinced about removing it. While most users only have one registry, we need to still support the multi-registry users. Uncommenting isn't accurate when doing things like running a "test latest versions" job in CI. It is also very handy to have a simple, universally applicable example in our documentation. This also keeps the way we talk about all of these things consistent.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||
|
|
||||||||||||
| It is also possible to configure the `min-publish-age` per cargo registry. `registries.<name>.min-publish-age` sets | ||||||||||||
| the minimum publish age for the `<name>` registry. And `registry.min-publish-age` sets it for the default registry | ||||||||||||
| crates.io registry. | ||||||||||||
|
|
||||||||||||
| [1]: https://doc.rust-lang.org/cargo/reference/config.html | ||||||||||||
| [^1]: As specified in `.cargo/config.toml` files | ||||||||||||
|
|
||||||||||||
| ## Reference-level explanation | ||||||||||||
| [reference-level-explanation]: #reference-level-explanation | ||||||||||||
|
|
||||||||||||
| This RFC adds a few new configuration options to [cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html). | ||||||||||||
|
|
||||||||||||
| ### Added to [Configuration Format](https://doc.rust-lang.org/cargo/reference/config.html#configuration-format) | ||||||||||||
|
|
||||||||||||
| ```toml | ||||||||||||
| [resolver] | ||||||||||||
| incompatible-publish-age = "fallback" # Specifies how resolver reacts to these | ||||||||||||
|
|
||||||||||||
| [registries.<name>] | ||||||||||||
| min-publish-age = "..." # Override `registry.global-min-publish-age` for this registry | ||||||||||||
|
|
||||||||||||
| [registry] | ||||||||||||
| min-publish-age = "0" # Override `registry.global-min-publish-age` for crates.io | ||||||||||||
| global-min-publish-age = "0" # Minimum time span allowed for packages from this registry | ||||||||||||
| ``` | ||||||||||||
|
|
||||||||||||
| ### Added to [`[resolver]`](https://doc.rust-lang.org/cargo/reference/config.html#resolver) | ||||||||||||
| #### `resolver.incompatible-publish-age` | ||||||||||||
|
|
||||||||||||
| * Type: String | ||||||||||||
| * Default: `"fallback"` | ||||||||||||
| * Environment: `CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE` | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| When resolving the version of a dependency to use, specify the behavior for versions with a `pubtime` (if present) that is incompatible with `registry.min-publish-age`. Values include: | ||||||||||||
|
|
||||||||||||
| * `allow`: treat pubtime-incompatible versions like any other version | ||||||||||||
| * `fallback`: only consider pubtime-incompatible versions if no other version matched | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| If the value is `fallback`, then cargo will print a warning if no suitable version can be found and the resolver is forced to select a version that is newer | ||||||||||||
| than allowed by the appropriate `min-publish-age` setting. | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| See the [resolver](https://doc.rust-lang.org/cargo/reference/resolver.html#rust-version) chapter for more details. | ||||||||||||
| ### Added to [`[registries]`](https://doc.rust-lang.org/cargo/reference/config.html#registries) | ||||||||||||
| #### `registries.min-publish-age` | ||||||||||||
|
|
||||||||||||
| * Type: String | ||||||||||||
| * Default: none | ||||||||||||
| * Environment: `CARGO_REGISTRIES_<name>_MIN_PUBLISH_AGE` | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| Specifies the minimum timespan since a version's `pubtime` that it may be considered for `resolver.incompatible-publish-age` for packages from this registry. If not set, `registry.global-min-publish-age` will be used. | ||||||||||||
|
|
||||||||||||
| Will be ignored if the registry does not support this. | ||||||||||||
|
|
||||||||||||
| It supports the following values: | ||||||||||||
|
|
||||||||||||
| * An integer followed by “seconds”, “minutes”, “hours”, “days”, “weeks”, or “months” | ||||||||||||
| * `"0"` to allow all packages | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| ### Added to [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry) | ||||||||||||
| #### `registry.min-publish-age` | ||||||||||||
|
|
||||||||||||
| * Type: String | ||||||||||||
| * Default: none | ||||||||||||
| * Environment: `CARGO_REGISTRY_<name>_MIN_PUBLISH_AGE` | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| Specifies the minimum timespan since a version's `pubtime` that it may be considered for `resolver.incompatible-publish-age` for packages from crates.io not set, `registry.global-min-publish-age` will be used. | ||||||||||||
|
|
||||||||||||
| It supports the following values: | ||||||||||||
|
|
||||||||||||
| * An integer followed by “seconds”, “minutes”, “hours”, “days”, “weeks”, or “months” | ||||||||||||
| * `"0"` to allow all packages | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| #### `registry.global-min-publish-age` | ||||||||||||
|
|
||||||||||||
| * Type: String | ||||||||||||
| * Default: `"0"` | ||||||||||||
| * Environment: `CARGO_GLOBAL_REGISTRY_<name>_MIN_PUBLISH_AGE` | ||||||||||||
|
|
||||||||||||
| Specifies the global minimum timespan since a version's `pubtime` that it may be considered for `resolver.incompatible-publish-age` for packages. If `min-publish-age` is not set for a specific registry using `registries.<name>.min-publish-age`, Cargo will use this minimum publish age. | ||||||||||||
|
|
||||||||||||
| It supports the following values: | ||||||||||||
|
|
||||||||||||
| * An integer followed by “seconds”, “minutes”, “hours”, “days”, “weeks”, or “months” | ||||||||||||
| * `"0"` to allow all packages | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| ### Behavior | ||||||||||||
|
|
||||||||||||
| In addition to what is specified above | ||||||||||||
|
|
||||||||||||
| * `min-publish-age` only apply to dependencies fetched from a registry, such as crates.io. They do not apply to git or path dependencies, in | ||||||||||||
| part because there is not always an obvious publish time, or a way to find alternative versions. | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
| * At this time, if a specific version is explicitly specified in Cargo.toml, or on the command line, that has higher precedence than the publish time check, | ||||||||||||
| and will be assumed to be valid. In the future it may be possible to change this behavior. | ||||||||||||
| * `cargo add` | ||||||||||||
| * If a version is not explicitly specified by the user and the package is fetched from a registry (not a path or git), `min-publish-age` options | ||||||||||||
| will be respected. | ||||||||||||
| * `cargo install` | ||||||||||||
| * If a specific version is not specified by the user, respect `registries.min-publish-age` for the version of the crate itself, | ||||||||||||
| as well as transitive dependencies when possible. | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MSRV resolver doesn't apply to
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a suggestion Granted, rust-lang/cargo#16694 might muddy the waters here
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I think that having this apply to There are also some significant differences between MSRV and this:
That said, I do think it would be surprising for a .cargo/config.toml in the package itself to impact the resolver when running So maybe we should specify that configuration from $CARGO_HOME/config.toml is used for checking minimum publish age in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We've already started down the path of documenting We can separately look into an Or we could look into more general fixes for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the mention entirely in 64f02f5.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we still need to talk about |
||||||||||||
| * `cargo update` | ||||||||||||
| * Unless `--precise` is used to specify a specific version, any crates updated from the registry will only consider versions published | ||||||||||||
| before the time specified by the appropriate `min-publish-age` option. If `--precise` is used, that version will be used, even if it | ||||||||||||
| newer than the policy would otherwise allow (although in the future, there may be an option to deny that). | ||||||||||||
| * If the version of a crate in the lockfile is already newer than `min-publish-age`, then `cargo update` will not update that crate, nor will | ||||||||||||
| it downgrade to an older version. It will leave the version as it is. | ||||||||||||
| * When a lockfile is generated, as with `cargo generate-lockfile` or other commands such as `cargo build` that can do so, then versions will be | ||||||||||||
| selected that comply with the `min-publish-age` policy, if possible. | ||||||||||||
| * If the only version of a crate that satisfies the `min-publish-age` constraint is a yanked version, it will behave as if no versions satisfied the | ||||||||||||
| `min-publish-age` constraint. In other words, yanked versions has higher priority than the `min-publish-age` configuration. | ||||||||||||
|
|
||||||||||||
| ## Drawbacks | ||||||||||||
| [drawbacks]: #drawbacks | ||||||||||||
|
Comment on lines
+258
to
+259
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||
|
|
||||||||||||
| The biggest drawback is that if this is widely used, it could potentially lead to it taking longer for problems to be discovered after a version is published. | ||||||||||||
| However, most likely, there will be a spread of values used, depending on risk tolerance, and hopefully the result is actually that there will be a more gradual rollout in most | ||||||||||||
| cases. | ||||||||||||
|
|
||||||||||||
| ## Rationale and Alternatives | ||||||||||||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||||||||||||
|
Comment on lines
+269
to
+270
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like there is content from the issue that isn't here, for example... we should talk about the reason we are going with I think the thread also covered that We also had conversions on naming that isn't here. e.g.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I think we talked about the use case for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I attempted to address this in 51e0a7c
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Switched to |
||||||||||||
|
|
||||||||||||
| ### Why not leave this to third party tools? | ||||||||||||
|
|
||||||||||||
| There are already some third party tools that fulfill this functionality to some degree. For example, dependabot and renovate can | ||||||||||||
| be used for updating Cargo.toml and Cargo.lock, and both support some form of minimum publish age. And the cargo-cooldown project provides | ||||||||||||
| an alternative to `cargo update` that respects a minimum publish age. | ||||||||||||
|
|
||||||||||||
| However, these tools only work for updating and adding dependencies outside of cargo itself, they do not | ||||||||||||
| have any impact on explicitly run built-in cargo commands such as `cargo update` and `cargo add`. | ||||||||||||
| Having built-in support makes it easier to enforce a minimum publish age policy. | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is easier, yes, but more importantly having this in Cargo is the only way to have it actually be secure. As soon as you run
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is only true if you don't have a Cargo.lock file, or your Cargo.lock file is missing something from your Cargo.toml file. But I did try to mention that in d78605c |
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| ### Configuration Locations | ||||||||||||
|
|
||||||||||||
| The locations and names of the configuration options in this proposal were chosen to be | ||||||||||||
| consistent with existing Cargo options, as described in [Related Options in Cargo](#related-options). | ||||||||||||
|
|
||||||||||||
| ## Prior Art | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing that's particularly relevant IMHO is how Right now, There are plenty of tools that other projects use to help mitigate some dependency issues that could definitely be helpful if they were part of Cargo. For example, the It would be nice if you could have an intermediary between
It's not clear whether all of these changes should be part of Cargo, but at least some of them could be, and we should make it easier to understand how things are being updated to avoid cases where someone accidentally updates to a vulnerable version. Minimum release time could still be one of the constraints added to packages under this system, but it wouldn't be the only one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think minimum release ages are only a (small) part of a larger toolbox here. I think the larger toolbox includes things like trusted packages/reviews as well as capability analysis (e.g.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If thats the case, I'm not sure I understand the concern. Yes, there are more things that we can be doing. Is it that the system for these rules isn't generalized like rust-lang/cargo#7193 ?
We have several different issues exploring different angles on this:
In my opinion, we should work to having vetting minimum dependencies. rust-lang/cargo#5657 includes two different ways to resolve to it to check it but I have concerns about each approach and I wonder if we should instead be more aggressive with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I almost deleted my last two replies so we could focus on the bigger topic in the first but didn't want to lose track of the links) |
||||||||||||
| [prior-art]: #prior-art | ||||||||||||
|
|
||||||||||||
|
tmccombs marked this conversation as resolved.
|
||||||||||||
| ### pnpm | ||||||||||||
|
|
||||||||||||
| `minimumReleaseAge` is a configuration option which takes a number of minutes as an argument. It then won't update or install releases that were released less than that many minutes ago. This also applies to transitive dependencies. | ||||||||||||
|
|
||||||||||||
| `minimumReleaseAgeExclude` is an array of package names, or package patterns for which the `minimumReleaseAge` does not apply, and the newest applicable release is always used. It also allows specifying specific versions to be allowed. | ||||||||||||
|
|
||||||||||||
| Both configuration options can be set in global config, a project-specific config file, or with environment variables (for a specific invocation). | ||||||||||||
|
|
||||||||||||
| ### yarn | ||||||||||||
|
|
||||||||||||
| Has a configuration setting that can be used in `.yarnrc.yml` named `npmMinimalAgeGate` that can be used to set the minimum age for installed package releases. It looks like it allows specifying units, as the example for three days is `3d`, however I haven't found any definitive description of the syntax. | ||||||||||||
|
|
||||||||||||
| As far as I can tell, there is no way to provide exclusions to this rule, or different times for different packages or repositories. | ||||||||||||
|
|
||||||||||||
| ### uv | ||||||||||||
|
|
||||||||||||
| The `--exclude-newer` option can be used to set a timestamp (using RFC 3339 format), or a duration (either "friendly" or ISO 8601 format) | ||||||||||||
| and won't use releases that happened after that timestamp. There is also an `--exclude-newer-package` option, which allows overriding the `exclude-newer` time for individual packages. | ||||||||||||
|
|
||||||||||||
| Both of these settings can also be used in the `uv` configuration file (`pyproject.toml`). | ||||||||||||
|
|
||||||||||||
| ### pip | ||||||||||||
|
|
||||||||||||
| Pip has an `--uploaded-prior-to` option that only uses versions that were uploaded prior to an ISO 8601 timestamp. Can also be controlled with the `PIP_UPLOADED_PRIOR_TO` | ||||||||||||
| environment variable. | ||||||||||||
|
|
||||||||||||
| ### dependabot | ||||||||||||
|
|
||||||||||||
| The `cooldown` option provides a number of settings, including: | ||||||||||||
|
|
||||||||||||
| - `default-days` – Default minimum age of release, in days | ||||||||||||
| - `semver-major-days`, `semver-minor-days`, `smever-patch-days` -- Override the cooldown/minimum-release-age based on what kind of release it is. | ||||||||||||
| - `include` / `exclude` – a list of packages to include/exclude in the "cooldown". Supports wildcards. `exclude` has higher priority than `include`. | ||||||||||||
|
|
||||||||||||
| "Security" updates bypass the `cooldown` settings. | ||||||||||||
|
|
||||||||||||
| Dependabot doesn't support cooldown for all package managers. | ||||||||||||
|
|
||||||||||||
| This is specified in the dependabot configuration file. | ||||||||||||
|
|
||||||||||||
| ### renovate | ||||||||||||
|
|
||||||||||||
| The options below can be provided in global, or project-specific configuration files, as a CLI option, or as an environment variable. | ||||||||||||
|
|
||||||||||||
| `minimumReleaseAge` specifies a duration which all updates must be older than for renovate to create an update. It looks like the duration specification uses units (ex. "3 days"), however, again I can't find a precise specification for the syntax. | ||||||||||||
|
|
||||||||||||
| I think it is possible to create separate rules with different `minimumReleaseAge` configurations. | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
| "Security" updates bypass the minimum release age checks. | ||||||||||||
|
|
||||||||||||
| ### deno | ||||||||||||
|
|
||||||||||||
| Deno supports a [configuration option](https://deno.com/blog/v2.6#controlling-dependency-stability) for `minimumDependencyAge` in the configuration file, or | ||||||||||||
| `--minimum-dependency-age` on the CLI. It supports an ISO-8601 duration, RFC 3339 timestamp, or an integer of minutes. | ||||||||||||
|
|
||||||||||||
| ### cargo-cooldown | ||||||||||||
|
|
||||||||||||
| There is an existing experimental third-party crate that provides a plugin for enforcing a cooldown: [https://github.com/dertin/cargo-cooldown] | ||||||||||||
|
|
||||||||||||
| ### Related Options in Cargo | ||||||||||||
| [related-options]: #related-options-in-cargo | ||||||||||||
|
|
||||||||||||
| Some precedents in Cargo | ||||||||||||
|
|
||||||||||||
| [`cache.auto-clean-frequency`](https://doc.rust-lang.org/cargo/reference/config.html#cacheauto-clean-frequency) | ||||||||||||
|
|
||||||||||||
| * "never" — Never deletes old files. | ||||||||||||
|
|
||||||||||||
| * "always" — Checks to delete old files every time Cargo runs. | ||||||||||||
|
|
||||||||||||
| * An integer followed by “seconds”, “minutes”, “hours”, “days”, “weeks”, or “months” | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| [`resolver.incompatible-rust-versions`](https://doc.rust-lang.org/cargo/reference/config.html#resolverincompatible-rust-versions) | ||||||||||||
|
|
||||||||||||
| * Controls behavior in relation to your [`package.rust-version`](https://doc.rust-lang.org/cargo/reference/rust-version.html) and those set by potential dependendencies | ||||||||||||
|
|
||||||||||||
| * Values: | ||||||||||||
|
|
||||||||||||
| * allow: treat rust-version-incompatible versions like any other version | ||||||||||||
| * fallback: only consider rust-version-incompatible versions if no other version matched | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| [`package.resolver`](https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions) is only a version number. When adding `incompatible-rust-version`, we intentionally deferred anything being done in manifests. | ||||||||||||
|
|
||||||||||||
| [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry) | ||||||||||||
|
|
||||||||||||
| * Set default registry | ||||||||||||
|
|
||||||||||||
| * Sets credential providers for all registries | ||||||||||||
|
|
||||||||||||
| * Sets crates.io values | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| [`[registries]`](https://doc.rust-lang.org/cargo/reference/config.html#registries) | ||||||||||||
|
|
||||||||||||
| * Sets registry specific values | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| `yanked`: can't do new resolves to it but left in if already there. Unstable support to force it with `--precise` but that doesn't apply recursively. | ||||||||||||
|
|
||||||||||||
| pre-release: requires opt-in through version requirement. Unstable support to force it with `--precise` but that doesn't apply recursively. | ||||||||||||
|
|
||||||||||||
| We use the term `publish` and not `release` | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| ## Unresolved Questions | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering how/if we expect this to interact with alternative date sources (some future, some partially already visible today). For example, if we implement something like TUF or a mirror of crates.io, the "age" here might want to be relative to the index snapshot we're using, rather than to absolute local time. Maybe there's a question worth adding about exploring what "now" is? As a concrete example of that, mirrors today can implement a version of this RFC by delaying imports of new versions by the period into the index they expose, rather than implementing that in Cargo. Fully replacing that with this RFC would require that the now timestamp is kept deterministic over time without a lock file checked in, which could be done if the index 'checkout' itself had the now timestamp stored when it was fetched. That would also help users opt-in more eagerly without needing that controlled at the mirror level, while still staying behind for most versions.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should include that as an alternative. Note that If we don't, for the unaware, we should call out that this relies on the already stable
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Putting it more concisely: the RFC as written I think assuming the relative age is relative to wall-clock now. I think registries should be able to tell Cargo when now is. For example, in offline mode, I think cargo ought to use the last time it fetched from the network, not continue ratcheting forward. (Or at least we should consider that...)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you help me understand what the use case is for this?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm on a flight, I don't really want Cargo suddenly realizing I have new crates "available" because they have matured in my index copy: I want to keep my index static and without the last N days as of when it was fetched. I think Cargo partially supports this with offline mode, but not completely: even if some newer crate is available locally, I probably don't want to start using it, because without an up to date index I can't know if it was yanked in the period of time since I fetched the index. My understanding is that our model is that automated scanners take 24-72 hours to pick up bad code in lots of cases. If I believe that I want to use the index as of -72 hours from when I fetched it, not -72 hours from whatever my local time is.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think anything like this could potentially be added later and feel like it should then be best left to a future possibility to keep this more narrowly scoped (for faster resolving of discussion and implementation). There also isn't a coherent snapshot of the index that we are operating on because of the sparse index design. We only update the entries we request and not all of those that have been downloaded. We'd have to keep per-entry "last update" times and make this relative to those. This is also adding a lot of writes even when the cache is unchanged which seems like it could affect performance and health of the drive (granted, target-dirs are even worse there) for something that won't be used in most cases. I'm also not aware of any prior art that tracks it this carefully. Some allow the user to set a date for comparison.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. naming:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes otherwise it could also be yank time (though crates.io doesn;t expose that yet)
The only use case I can imagine is rust-lang/cargo#5221, which we have the unstable If this doesn't have room for absolute times, we might need something like |
||||||||||||
| [unresolved-questions]: #unresolved-questions | ||||||||||||
|
|
||||||||||||
| * Should "deny" be an allowed value for `resolver.incompatible-publish-age`? And if so, how does that behave? What is the error message? Is it overridden | ||||||||||||
|
epage marked this conversation as resolved.
Outdated
|
||||||||||||
| by a version specified in `Cargo.lock` or with the `--precise` flag? | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And how would it interact with important urgent security updates?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've removed this from open questions, and left it as just a Future possibility for now. |
||||||||||||
| * Would it be better to have `registry.min-publish-age` be the global setting, and `registries.crates-io.min-publish-age` be the setting for the crates.io registry? | ||||||||||||
| The current proposal is based on precedent of "credential-provider" and "global-credential-provider", but perhaps we shouldn't follow that precedent? | ||||||||||||
|
Comment on lines
+484
to
+485
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading the RFC I found myself wondering what "global-" meant here, since in the rest of Cargo config docs "global" refers to user settings ("global cache", "global git configuration", "globally change profile settings"). So I don't think the precedent is great?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, there is already precedence that |
||||||||||||
| * How do we make it clear when things are held back? | ||||||||||||
| * The "locking" message for [Cargo time machine (generate lock files based on old registry state) #5221](https://github.com/rust-lang/cargo/issues/5221) lists one time but the time here is dependent on where any given package is from | ||||||||||||
| * We list MSRVs for unselected packages, should we also list publish times? I'm assuming that should be in local time | ||||||||||||
| * Locking message for [Cargo time machine (generate lock files based on old registry state) #5221](https://github.com/rust-lang/cargo/issues/5221) is in UTC time, see [Tracking Issue for _lockfile-publish-time_ #16271](https://github.com/rust-lang/cargo/issues/16271), when relative time differences likely make local time more relevant | ||||||||||||
| * Implementation wise, will there be much complexity in getting per registry information into `VersionPreferences` and using it? | ||||||||||||
| * `fallback` precedence between this and `incompatible-rust-version`? | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo unspecified though I would put |
||||||||||||
|
|
||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another question worth pointing out: "Are we comfortable making the security guarantees that the build remains secure when a not-yet-of-age malicious update has been released?". I think the answer here ought to be yes, but that's a decision that should be made deliberately and knowingly.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I worry when "making [a] security guarantee" on whether we are actually committing to that level (ie a report needs to be issued and a hotfix released).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any such guarantee would need to be predicated on some conditions, including:
|
||||||||||||
| ## Future Possibilities | ||||||||||||
| [future-possibilities]: #future-possibilities | ||||||||||||
|
|
||||||||||||
| - Support "deny" for `resolver.incompatible-publish-age`. | ||||||||||||
| - This is initially excluded, because it isn't clear how this should behave with respect to versions already in Cargo.lock, or use with the `--precise` flag. | ||||||||||||
| - Add a way to specify that the minimum age doesn't apply to certain packages | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RFC doesn't clearly lay out the expected way to deal with urgent security updates.
From my reading of it, I would deal with those by using
cargo update --preciseor putting it in Cargo.toml, getting a warning, and then after having the new version in the lockfile, everything works normally.But this would mean that when regenerating the lockfile (when
update --precisewas used instead of Cargo.toml), this security update would be lost silently, which sounds suboptimal. Having an exclude array like pnpm does not have this problem.Can you add why you decided against package exclusion configuration and whether this threat of regenerating the lockfile after a security update is important?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is something you depend on, you should raise your version requirement.
But that is a heavy hammer. It doesn't record why it was excluded and you can easily forget to un-exclude when it is no longer applicable. With raising a version requirement, you automatically start using the feature again with the dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did include that in the Future Possibiliites, but I could probably expand on that more.