Repository navigation
proposal: cmd/go: support dependency cooldown in Go tooling #76485
Description
Activity
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
- addedToolProposalIssues describing a requested change to a Go tool or command-line program.Issues describing a requested change to a Go tool or command-line program.
on Nov 27, 2025 This is probably something best left to external tools which can combine multiple information feeds to make a decision for upgrades. Note that in Go there isn't a great source of when a package is published since there's not a registry you publish to.
Also, the design of MVS means it should be rare for you to be running the latest versions of deep dependencies, which is where a lot of these attacks are from.Reacted by Fumin, zhaolei, Pieter Oliver, Tamás Gulácsi, Florian Lehner and Thomas FrössmanReacted by Jorropo, Ayu, Oleg Kovalov, Brandon Bennett, James Johns and gopheroThis is fair enough. It may warrant a new external CLI tool in addition to bots. It could wrap the
gocommand and offer a pre-commit check that uses external data to warn about new dependencies.I think this is a fair thing to discuss, even tho we will follow
go.mod&go.sumwhen possible,
I believego mod tidyorgo getcould benefit from such flag.How would the implementation work tho ?
If let's say for git tags we could use the timestamp in the tag but then an attacker could just back-date them.What if
go.modis newer than the cooldown ?
Let's say a coworker update a lib to something released 3 days ago ?Reacted by Oleg Kovalov and firengate- addedWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.Issue is not actionable because of missing required information, which needs to be provided.
on Nov 28, 2025 We've been discussing this privately among @golang/security for a few months. It'd probably involve inserting a first-observed timestamp in sumdb entries, and using that in
go getandgo mod tidy.Reacted by Jorropo, D. Bohdan, Oleg Kovalov, Eric Cornelissen, Ufuktan Yildirim, Adam Jones, Brian Goff, firengate, Sylvain Muller, Romain Beuque and 1 more- added and removedWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.Issue is not actionable because of missing required information, which needs to be provided.
on Dec 13, 2025 The proxy
.infoendpoints do contain the first observed timestamp,
but doing the filtering client side seems way too inefficient,
and there's nothing you can do for direct vcs access.Rather, I think if people want trusted timestamps,
it's much easier for someone to run a filtered proxy.
It only needs to read off index.golang.org or similar,
and serve filtered/listresponses of available versions.
Serving all other content can just be redirects to proxy.golang.org.Agreed that efficient implementation will need changes to /list, too, although it might be enough to return timestamps for the versions.
However, the threat model has always been that the proxy is untrusted and can only “hold back time.” If we build this as a first class feature, we’ll want to backstop it in the sumdb, so that a lying proxy still can’t make “go get” install a recent version.
The custom lightweight proxy is a cool idea though, we might host it as Geomys to prototype this.
Reacted by Jorropo, Grigorii Khvatskii, D. Bohdan, Sean Russell, Aitva and firengate20 remaining items
As Filippo said, we've been discussing what this could look like for a while, but we got beaten to the punch by @dbohdan in posting a proposal.
To provide some context on our discussions so far, I think there are really two interconnected questions here (only one of which likely needs a proposal).
The first question is if we should add a way to ask the module proxy to (partially) omit information newer than some cutoff. The "partially" here is somewhat important, as we don't want to prevent explicit requests for specific version information, which would prevent explicit upgrades for security reasons, etc. In practice we would likely only want this to impact
/$module/@latestand/$module/@v/list.I think @rsc is correct in that we probably want the request mechanism to be a path prefix, similar to
/cached-only/, rather than a header. This would experimentation with this feature without any toolchain changes, as users would be able to setGOPROXY=https://https://proxy.golang.org/cooldown/2dand the toolchain would simply behave as expected.As with anything, what to name the path prefix is up for debate. I've been using
/cooldown/internally, but/delay/, or something else all work.There are also questions about whether we should impose limits on the min/max delay, and if there should be a fixed granularity (e.g. you can request between 1 and 30 days in 1 day increments), but I'm not sure those need to be answered here.
I don't believe this first question requires a proposal. The specific design of the module proxy API is not really covered by the compatibility promise, and I believe we are free to extend it at will (adding support for the delay would also not affect any of the base endpoints, regardless).
Another question that has come up is should the Go team be providing this functionality at all, vs. allowing a third-party to implement it. As @rsc mentioned, the module proxy has a significant advantage here, as we already have all of the information necessary to implement this as a first-class feature. Lightweight proxies are possible to implement (I have done this myself as an exercise in trying to figure out if it was doable), but there are a number of pitfalls to implementing it correctly that do not make it particularly straightforward.
Given we are seeing increasing industry demand for this kind of dependency management processes, and since the module proxy is already load bearing infrastructure for Go dependency management, it seems entirely reasonable for us to provide this functionality, rather than forcing Go users to rely on third-party implementations.
The second question is, if we decide to add module proxy support for this feature, whether we should also add additional toolchain support, beyond simply setting GOPROXY, and what that support should look like. I think this is significantly more complex, and likely does require a concrete proposal.
I'm not sure we have a great idea of what this would look like yet. The most obvious solution is to simply add a new envvar, e.g. GOMODCOOLDOWN, which can be set to a duration which we pass to the module proxy. Of course there are then additional questions, such as what do we do if GOPROXY is set to a non-standard value, and how to support things such as GOPRIVATE, or another new cooldown specific envvar to skip cooldowns for specific modules.
These are all interesting questions, but I don't think we explicitly need to answer them before deciding on/implementing module proxy support, and in fact having that support in place is likely to allow us to experiment in ways that will inform how/if we actually want more fine-grained toolchain support and what that support should look like, as Russ mentioned.
Reacted by Michael Matloob, firengate, Tom Thorogood and Will FaughtI don't have any concrete suggestions here, but it seems to me that there are at least two different scenarios for updating a dependency:
- I want to get a more recent version to acquire any fixes or improvements that happened since my last update, but I don't have any specific desires other than to stay reasonably up-to-date.
- I want to get the latest version (or a version >= X.Y.Z), because that version includes a change important to me (new feature, bugfix, etc.).
The first case might benefit from dependency cooldown. The second does not.
GOPROXY=https://proxy.golang.org/cooldown/2dseems like it would interfere with the second case; I can easily imagine a frustrated user who has forgotten they enabled cooldown being confused as to why thegocommand is insisting that a new version of some module does not exist.Can we reasonably infer which intent a user has when updating a module? Perhaps
go get -ucould apply cooldown, whilego get $MODULEdoes not. Andgo get -u $MODULEwould get the latest version of $MODULE, but apply cooldown to the module's dependencies. We could print an informational message when cooldown is being applied:$ go get -u go: applying 10d dependency cooldown go: upgraded golang.org/x/net v0.50.0 => v0.51.0 (latest is v0.52.0) $ go get golang.org/x/net go: upgraded golang.org/x/net v0.51.0 => v0.52.0Again, this wouldn't be something we can support just by setting GOPROXY.
Reacted by Will FaughtRelated to #56157 as other tools like govulncheck would need to be aware of releases to ignore which are newer than the delay duration.
Related to #56157 as other tools like govulncheck would need to be aware of releases to ignore which are newer than the delay duration.
I'm not sure we'd want this type of tooling to ignore things. The design of the cooldown mechanism is intended to allow explicit requests for versions that are beyond the false horizon, specifically because it would allow pulling in necessary security fixes. Pretending that security vulnerabilities in a program don't exist doesn't seem to provide any real benefit (and seems in fact to be quite counter productive).
I agree that ignoring security fixes wouldn't be a good outcome of this, but would cooldowns let versions with a GO vuln through regardless of age?
If I have one tool saying there's no newer version but another tool requiring an update to a version the first can't tell me about that will pose a problem.
There have been languages/libraries who introduced new security flaws when patching existing non-security bugs, so not upgrading in those niche caches would be safer than upgrading until all known flaws are patched.
I personally use
govulncheckto find out whether I need to make any upgrades, separately from actually performing those upgrades. Then I review the advisory and, if it seems valid, rungo get package@versionto upgrade to the specific fix version mentioned in the advisory. So at least for for me it'd work out well forgovulncheckto ignore cooldown and just show me everything, as long as the non-security-related tools for finding possible upgrades (go get -u,go list -m -u) do respect the cooldown setting.It does seem true though that anyone who immediately adopts an upgrade just because it has an advisory attached to it would be vulnerable to being manipulated by someone who is capable of publishing both a malicious release and a malicious advisory. So perhaps something in this chain (either
govulncheckorgo get) should produce a brief note if the proposed/selected version is one that would not have been permitted under the cooldown rule, so that the person running the commands at least knows there's some additional risk here, but I don't think it's helpful to either completely hide an advisory or to prevent upgrading to its fixed version.Reacted by Adam Jones, Rafi H., firengate and Tom ThorogoodI'm very keen on this being implemented: supply chain attacks are only getting more common in the npm world (SHA1-Hulud, Axios, and just today Bitwarden-CLI)
Package management systems across many languages have been implementing these cooldown settings to allow users some security against supply chain compromises: https://cooldowns.dev/
While javascript and python have separate package managers in npm and pip, I'd say most Golang users install packages using the default
gocommand.Reacted by Robin and mvgrimesAfter implementing and experimenting with a cooldown proxy in Source Spotter this weekend 1, I realized that filtering the
/listresponse interacts poorly with retraction./listcan return a retracted version, but not the newer version which declares the retraction, and the go command will download the retracted version when using the@latestversion query.Maybe the proxy, when in cooldown mode, should be aware of retractions and exclude retracted versions from the
/listresponse?Footnotes
-
Publicly available at
GOPROXY=https://cooldown.api.sourcespotter.com/{N}dif others want to experiment. Unlike lightweight proxies, it doesn't rely on VCS timestamps; the cooldown timer starts when Source Spotter first observes the version in the sumdb, which (if you trust us) is a few minutes after the sumdb first observes it. ↩
Reacted by Sylvain Muller, Sean Flanigan and Jamie Tanna-
- added a commit that references this issue
on May 13, 2026 Placed on hold.
— aclements for the proposal review groupFor context: we're going to see what it looks like to implement this at the module proxy layer first, as a URL path prefix. Users will then be able to use the cooldown version of the proxy by setting their GOPROXY env var. If that design ends up making sense, we'll revisit what it'd look like to add simpler toolchain support.
Reacted by Adam Shannon and Will FaughtReacted by Jason Hall, D. Bohdan, Alexey Palazhchenko, Tom Thorogood, originalsouth, Jamie Visker, Nathan Cochran and Zaulao
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
- StatusShow more project fieldsHold
Proposal Details
Background
Supply chain attacks on open-source software dependencies have become a regular occurrence. Go already takes measures against supply chain attacks. While Go libraries aren't a common target, this year GitLab detected one attack that involved typosquatting.
The idea of a "dependency cooldown" is to not download dependencies that are too new. The theory behind it is that most supply chain attacks in their active phase are detected quickly, so they are possible to avoid by waiting. The blog post "We should all be using dependency cooldowns" presents empirical evidence in favor of this based on recent (non-Go) attacks.
Proposal
When a cooldown is configured, commands like
go getandgo mod tidywould only consider dependency versions that are at least N days, weeks, or months old.This could be controlled with an environment variable:
With this setting, Go would skip any dependency versions published less than 15 days ago when resolving dependencies.
Alternatives
The primary alternative is to use external tools like Dependabot that already support this.
Open questions
The design naturally invites questions about scope:
go.mod?