Dependency age gate for NuGet

Enforce a package cooldown for NuGet.

NuGate fails the build when any resolved package — direct or transitive — was published less than N days ago. Default 7. Add an explicit allowlist for the exceptions.

free · open source (Apache-2.0) · runs on your machine and in CI

nugate.jsonminAgeDays 7·mode enforce·onApiFailure fail

GitHub ↗The cooldown guide →

Install

Two ways in, same engine. Add one package reference to gate every project in a repo, or run the CLI as a CI step before the build.

One line in Directory.Build.props gates every project in the repo — dev machines included.

Directory.Build.props
<Project>
  <ItemGroup>
    <PackageReference Include="NuGate.Build" Version="0.1.2" PrivateAssets="all" />
  </ItemGroup>
</Project>

For CI, install the tool and run nugate checkbetween restore and build — before any package's MSBuild targets can execute.

bash
# install once
dotnet tool install -g NuGate.Tool

# in CI, between restore and build:
dotnet restore
nugate check
dotnet build

What a failure looks like

A transitive dependency resolved to a version published 2.1 days ago. The policy is 7. The build stops and prints the exact allowlist entry to override it.

dotnet build
$ dotnet build
  Determining projects to restore...
  Restored C:\src\Vantage.Orders\Vantage.Orders.csproj (in 1.8s).
  NuGate.Build 0.1.0 — checking resolved package ages against nugate.json...

C:\src\Vantage.Orders\Vantage.Orders.csproj : error NUGATE001:
  Package 'Northwind.Http.Retry' 2.4.1 fails the dependency age policy.

    Resolved via: Vantage.Orders.Api -> Vantage.Orders.Core -> Northwind.Http.Retry 2.4.1 (transitive)
    Published (catalog 'created'): 2026-07-13T02:41:09Z
    Age at build time: 2.1 days
    Policy: minAgeDays = 7 (nugate.json, mode: enforce)

  To allow this version explicitly, add to nugate.json:

    {
      "id": "Northwind.Http.Retry",
      "version": "2.4.1",
      "expires": "2026-08-01",
      "reason": "hotfix"
    }

Build FAILED.

    1 Error(s)

Time Elapsed 00:00:04.62

What it does, and what it doesn't

What it does

  • Enforces a minimum age on every resolved package version — direct and transitive.
  • Reads the full restore graph, so floating and transitive versions are covered.
  • Fails the build (or warns) with the package, version, age, and override syntax.
  • Runs on dev machines and in CI. No proxy, no server, no telemetry.

What it does not do

  • Does not detect malware. It checks age, not contents.
  • A 30-day-old compromised package passes by design.
  • Cooldowns shrink the attack window. They do not close it.
  • No CVE scanning, no accounts, no dashboard.

How it works

  1. Reads obj/project.assets.json — the full resolved dependency graph after restore, transitive packages included.

  2. Resolves each version's immutable catalog created timestamp from the nuget.org API, cached locally forever.

  3. Compares every package's age against your policy (minAgeDays).

  4. Fails the build with an actionable list: package, version, age, policy, and the exact allowlist entry to override it.

Why created and not published? nuget.org resets published to 1900-01-01 when a package is unlisted — and compromised versions are usually unlisted after takedown. A naive published-date check would wave the worst packages through as ancient. The immutable catalog createdtimestamp can't be reset.

Configuration

A single nugate.json at the repo root. Every field has a sensible default; an empty file is a valid 7-day policy.

nugate.json
{
  "minAgeDays": 7,
  "mode": "enforce",
  "onApiFailure": "fail",
  "allow": [
    { "id": "SomePackage", "version": "3.1.4", "expires": "2026-08-01", "reason": "hotfix" }
  ],
  "exemptPrefixes": ["MyCompany."]
}
minAgeDays
Minimum age, in days, for any resolved package version. Default 7.
mode
enforce fails the build on a violation. warn reports without failing — use it to roll out on an existing repo.
onApiFailure
What to do when nuget.org can't be reached. fail is the default (fail-closed); warnopts out for shops that won't tolerate an outage breaking builds.
allow
Explicit exceptions by id and version. expires keeps an exception from fossilizing; reasonrecords why it's there.
exemptPrefixes
Package id prefixes to skip — internal and private-feed packages whose timestamps aren't on nuget.org.

FAQ

What happens if nuget.org is down?

Fail-closed by default: onApiFailure is fail, so an unreachable API stops the build rather than waving packages through. Set it to warnif you'd rather tolerate outages than block builds. Timestamps are immutable and cached, so a warm cache keeps working through most outages anyway.

What about private or internal packages?

Their timestamps aren't on nuget.org, so add their id prefixes to exemptPrefixes. NuGate skips them instead of failing on a lookup it can't make.

Do floating versions get checked?

Yes. NuGate reads the resolved version from project.assets.json after restore, so whatever a float like 1.* actually resolved to is what gets checked.

Isn't this just Dependabot or Renovate cooldown?

Those gate bot PRs. NuGate gates everyone — including a human running a manual dotnet add package or editing a .csproj by hand, which is the path a bot never touches.

Why not run a proxy instead?

A proxy means standing infrastructure and repointing every dev machine and CI runner at it. NuGate adds one package reference or one CI step. No server, no infra, nothing to keep running.

Does this replace a vulnerability scanner?

No. NuGate checks age, not contents — it's a policy gate, not a scanner. Run it alongside CVE and malware tooling, not instead of them.