A small, ecosystem-agnostic version-age filter for package-manager tools. Hides versions published too recently so the community has time to spot malicious releases before they're pulled into projects.
Cross-ecosystem by construction: the same Config shape covers npm, PyPI, Cargo, RubyGems, Composer, Conda, Hex, NuGet, Pub, and any future ecosystem. Resolution order is package-PURL > ecosystem-name > global default, so a single config can express a strict default with targeted opt-outs.
go get github.com/git-pkgs/cooldown
cfg := &cooldown.Config{
Default: "48h", // global window
Ecosystems: map[string]string{"npm": "72h"}, // npm gets longer
Packages: map[string]string{ // per-PURL override
"pkg:npm/htmx.org": "0", // 0 = disabled
},
}
if cfg.IsAllowed("npm", "pkg:npm/lodash", publishedAt) {
// version cleared the window; use it
}
decision := cfg.Evaluate("npm", "pkg:npm/lodash", publishedAt, evaluatedAt)
if !decision.Allowed {
fmt.Printf("available at %s (%s)\n", decision.AvailableAt, decision.Reason)
}Config.Evaluate accepts an explicit evaluation time and returns the selected cooldown, availability time, and a typed reason. Versions without a publication time remain allowed and return ReasonUnknownPublicationTime. Config.IsAllowed is the current-time shorthand. Config.For(ecosystem, purl) returns the effective duration; useful when surfacing the policy to a UI. Config.Enabled() reports whether any cooldown is configured (cheap check before walking a large version set).
Duration strings accept Go's standard formats (48h, 30m, 1h30m) plus a d suffix for days (3d). 0 disables the window.
For package-pattern overrides, construct a Policy before serving requests:
policy, err := cooldown.NewPolicy(cfg, map[string]string{
"pkg:npm/@example/*": "0",
"pkg:nuget/example.*": "7d",
})
if err != nil {
return err
}
decision := policy.Evaluate("npm", "pkg:npm/%40example/widget", publishedAt, evaluatedAt)Policy provides the same For, Evaluate, IsAllowed, and Enabled methods as Config. Exact package overrides take precedence over patterns, followed by ecosystem overrides and the global default. The matching pattern with the most literal characters wins, with lexical order breaking ties. * and ? do not match /; character classes and escapes are rejected.
Patterns accept either @ or %40 for npm scopes. Exact keys and lookup PURLs must already use the same canonical encoding. The constructor rejects invalid pattern durations and conflicting normalized patterns; equivalent durations such as 1d and 24h are accepted. A nil base config supplies a zero default. Construct the policy once and leave the base config unchanged while using it concurrently.
Originally lived inside an HTTP proxy that filtered registry responses; spotted as a reusable shape and lifted out. The same predicate is useful for package-manager CLIs, dependency-update bots, security scanners, and anything else that walks a version set with an age constraint.
MIT.