Dependency Update Policy

One line: take security fixes promptly; don’t chase non-security version bumps, and when you do take them, let releases age first. This is enforced with Dependabot only — one tool, per-repo config, no Renovate.

The Policy

Update typeBehaviorYour job
Security (CVE/GHSA)PR created immediately — cooldown never delays security updatesMerge promptly: critical/high same-day; moderate/low may bake 2–3 days
Version (routine)PR created only after a release-age cooldown (npm: 30/14/7 days for major/minor/patch; other ecosystems: 14 days). Minor+patch grouped into one weekly PRMerge during normal maintenance; no urgency

Why the cooldown: a brand-new release is the highest-risk artifact in the supply chain — compromised-maintainer payloads and botched releases are usually detected (and yanked or .1-patched) within days. Letting releases bake avoids adopting them in that window. Security PRs are exempt because a known vulnerability outweighs a speculative bad release; the compensating control is that a human merges the PR (and can glance at the dependency diff — a “security fix” touching install scripts or adding network calls is a red flag).

Why not Renovate: the org runs on progressivesurface.ghe.com (GitHub Enterprise Cloud with data residency), where Mend’s hosted Renovate app is unavailable — Renovate would mean self-hosting a bot. Dependabot’s native cooldown option (GA July 2025) covers the release-age requirement with zero infrastructure. The Renovate rollout referenced in older configs/docs was dropped 2026-07-27.

Configuration

Rolled out to all active repos with dependency manifests on 2026-07-27. Dependabot version-update config is per-repo by design — there is no org-wide dependabot.yml (only private-registry config and security-update enablement can be centralized). New repos must add .github/dependabot.yml:

# Dependabot config — security updates (immediate) + version updates (cooldown)
#
# Security PRs (CVE/GHSA) are NOT subject to cooldown and flow immediately —
# merge them promptly. Version updates wait out a release-age cooldown so new
# releases bake before adoption (supply-chain / regression guard).
# Verify: Repo Settings → Code security → Dependabot security updates = ENABLED.
# Policy: PSI Wiki → development/dependency-update-policy
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5
    cooldown:
      default-days: 14
      semver-major-days: 30
      semver-minor-days: 14
      semver-patch-days: 7
    groups:
      npm-minor-patch:
        applies-to: version-updates
        update-types:
          - "minor"
          - "patch"
 
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5
    cooldown:
      default-days: 14
    groups:
      actions-all:
        applies-to: version-updates
        patterns:
          - "*"

Ecosystem notes

  • npm is the only ecosystem with semver-granular cooldown (semver-major/minor/patch-days); for pip, nuget, docker, gradle, github-actions use cooldown: default-days: 14 only.
  • Multi-project .NET repos: use directories: ["/**"] on the nuget block.
  • Monorepos/workspaces: list each manifest directory (globs allowed, e.g. "/packages/*").
  • Lockfiles are the real exposure control — CI and deploys must use npm ci (or equivalent) so nothing enters production except through a reviewed PR.

Gotchas

Learned the hard way in psi-portal (2026-08-10); all of it applies to every repo on this config:

  • A comment in .npmrc can kill the updater outright. Dependabot’s npm-config reader does not honour #. psi-portal’s .npmrc illustrated the auth entry it deliberately omits using an ellipsis character as a stand-in for the registry host; Dependabot read that comment as a registry entry and every run died on dependency_file_not_resolvable {message: "URI must be ascii only ..."}. Total failure, not per-package — no security update could be prepared at all, for six weeks, while 38 alerts accumulated. Keep .npmrc ASCII and never write a host-and-token line in it, even as illustration.
  • A broken updater is nearly silent. The only symptom is a failed run under Actions → the Dependabot tab, which nobody watches. Alert counts keep climbing normally, so the security tab looks like a triage backlog rather than a broken pipeline. Worth a glance whenever a repo’s Dependabot PRs go quiet for a few weeks.
  • ignore has a safe form and an unsafe one. update-types: ["version-update:semver-major"] is documented to affect version updates only — security PRs for a major still arrive. Ignoring by versions: range suppresses security updates in that range too, which is how a CVE ends up producing no PR at all. Prefer update-types; treat a versions: ignore as a decision to accept the risk in that range.
  • open-pull-requests-limit does not cap security PRs, only version-update PRs. A repo can sit well past its limit legitimately.

Intentional exclusions

  • PSI.All, PSI.BC.ALL — legacy vendored .NET Framework Trunk layouts; nuget version-bump PRs are unwanted noise (PSI.All still gets github-actions updates). Security alerts still surface via the dependency graph.
  • Repos with no dependency manifests and no workflows carry no config.

History

  • Pre-2026-07: old template (limit 5, no cooldown) documented in deploy-to-azure; enacted configs had drifted to security-only (open-pull-requests-limit: 0) pending a Renovate rollout that never shipped.
  • 2026-07-27: Renovate plan dropped; cooldown-enabled Dependabot config rolled out org-wide (~45 repos).