Skip to content

proposal: cmd/go: support dependency cooldown in Go tooling #76485

Description

@dbohdan

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 get and go mod tidy would only consider dependency versions that are at least N days, weeks, or months old.

This could be controlled with an environment variable:

GOCOOLDOWN=15d go mod tidy

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:

  1. Should this apply to all dependencies equally?
  2. Should the cooldown interval be set in the development environment or in go.mod?

Activity

  1. added this to the Proposal milestone on Nov 27, 2025
  2. gabyhelp commented on Nov 27, 2025

    @gabyhelp

    Related Issues

    (Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

  3. added
    ToolProposalIssues describing a requested change to a Go tool or command-line program.
    on Nov 27, 2025
  4. seankhliao commented on Nov 27, 2025

    @seankhliao
    Member

    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.

  5. dbohdan commented on Nov 27, 2025

    @dbohdan
    Author

    This is fair enough. It may warrant a new external CLI tool in addition to bots. It could wrap the go command and offer a pre-commit check that uses external data to warn about new dependencies.

  6. Jorropo commented on Nov 28, 2025

    @Jorropo
    Member

    I think this is a fair thing to discuss, even tho we will follow go.mod & go.sum when possible,
    I believe go mod tidy or go get could 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.mod is newer than the cooldown ?
    Let's say a coworker update a lib to something released 3 days ago ?

  7. added
    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.
    on Nov 28, 2025
  8. FiloSottile commented on Dec 13, 2025

    @FiloSottile
    Contributor

    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 get and go mod tidy.

  9. added and removed
    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.
    on Dec 13, 2025
  10. seankhliao commented on Dec 13, 2025

    @seankhliao
    Member

    The proxy .info endpoints 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 /list responses of available versions.
    Serving all other content can just be redirects to proxy.golang.org.

  11. FiloSottile commented on Dec 13, 2025

    @FiloSottile
    Contributor

    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.

  12. 20 remaining items

  13. rolandshoemaker commented on Mar 24, 2026

    @rolandshoemaker
    Member

    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/@latest and /$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 set GOPROXY=https://https://proxy.golang.org/cooldown/2d and 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.

  14. neild commented on Mar 24, 2026

    @neild
    Contributor

    I don't have any concrete suggestions here, but it seems to me that there are at least two different scenarios for updating a dependency:

    1. 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.
    2. 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/2d seems 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 the go command 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 -u could apply cooldown, while go get $MODULE does not. And go get -u $MODULE would 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.0
    

    Again, this wouldn't be something we can support just by setting GOPROXY.

  15. adamdecaf commented on Mar 25, 2026

    @adamdecaf
    Contributor

    Related to #56157 as other tools like govulncheck would need to be aware of releases to ignore which are newer than the delay duration.

  16. rolandshoemaker commented on Mar 25, 2026

    @rolandshoemaker
    Member

    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).

  17. adamdecaf commented on Mar 25, 2026

    @adamdecaf
    Contributor

    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.

  18. apparentlymart commented on Mar 25, 2026

    @apparentlymart

    I personally use govulncheck to 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, run go get package@version to upgrade to the specific fix version mentioned in the advisory. So at least for for me it'd work out well for govulncheck to 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 govulncheck or go 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.

  19. Tim-Blyth-SiteHost commented on Apr 24, 2026

    @Tim-Blyth-SiteHost

    I'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 go command.

  20. AGWA commented on May 4, 2026

    @AGWA

    After implementing and experimenting with a cooldown proxy in Source Spotter this weekend 1, I realized that filtering the /list response interacts poorly with retraction. /list can 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 @latest version query.

    Maybe the proxy, when in cooldown mode, should be aware of retractions and exclude retracted versions from the /list response?

    Footnotes

    1. Publicly available at GOPROXY=https://cooldown.api.sourcespotter.com/{N}d if 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. ↩

  21. aclements commented on Jun 25, 2026

    @aclements
    Member

    Placed on hold.
    — aclements for the proposal review group

  22. moved this from Active to Hold in Proposalson Jun 25, 2026
  23. rolandshoemaker commented on Jun 25, 2026

    @rolandshoemaker
    Member

    For 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions