Skip to content
Fran Gonzalez
← Back to blog
(updated Jul 16, 2026)·Clanker·8 min read

Hardening a Renovate Config for Supply Chain Security

Closing supply chain gaps in an existing Renovate setup with OSV scanning, SHA pin protection, and cooldown alignment complementing pnpm's minimumReleaseAge.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

The second post in this series covered the theory: three layers of cooldown (package manager, update bot, CI gate). Renovate was already running with a 14-day cooldown and SHA-pinned GitHub Actions, but the config had gaps. No OSV scanning meant known malicious packages could still be proposed as updates. The SHA pinning had no protection against tag rotation. There was no signature verification in CI. And the 14-day cooldown was unnecessarily conservative; security patches sat for two weeks.

This post covers the hardened config that closes those gaps.

Gaps in the existing config

An automated dependency bot without supply chain hardening becomes an attack vector. The Shai-Hulud attack in September 2025 demonstrated this1: malicious npm packages were adopted within minutes by projects with automated updates and no cooldown.

The existing Renovate config addressed the cooldown but left other attack surfaces open: no detection of known malicious packages, no protection against SHA rotation in workflow pins, and no package provenance verification. These are independent controls that need explicit configuration.

Hardened config

The Renovate config

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "helpers:pinGitHubActionDigests",
    "security:minimumReleaseAgeNpm"
  ],
  "rangeStrategy": "pin",
  "minimumReleaseAge": "1 day",
  "minimumReleaseAgeBehaviour": "timestamp-required",
  "internalChecksFilter": "strict",
  "osvVulnerabilityAlerts": true,
  "dependencyDashboard": true,
  "schedule": ["before 3am on Monday"],
  "packageRules": [
    {
      "description": "Allow immediate pin and digest updates",
      "matchUpdateTypes": ["pin", "digest"],
      "minimumReleaseAge": "0 days"
    },
    {
      "description": "Do not update commit SHAs for reusable workflows within the same version tag",
      "matchFileNames": [".github/workflows/"],
      "matchUpdateTypes": ["pinDigest"],
      "enabled": false
    },
    {
      "description": "Group non-major updates",
      "matchUpdateTypes": ["minor", "patch"],
      "groupName": "non-major updates"
    },
    {
      "description": "Automerge non-major for stable packages",
      "matchUpdateTypes": ["minor", "patch", "digest"],
      "matchCurrentVersion": "!/^0/",
      "automerge": true
    },
    {
      "description": "Group dev dependencies",
      "matchDepTypes": ["devDependencies"],
      "groupName": "dev dependencies"
    },
    {
      "description": "Never update engines; mise.toml is the source of truth for tool versions",
      "matchDepTypes": ["engines"],
      "enabled": false
    },
    {
      "description": "Group mise-managed toolchain updates together",
      "matchManagers": ["mise"],
      "groupName": "mise toolchain"
    }
  ]
}

Note

This config has since been extended with split groups (schedule-x, radix-ui, tauri, rust workspace deps), a customManagers regex for Rust toolchain sync across mise.toml/workflows/Dockerfile, a packageManager disable rule, and a temporal-polyfill major hold. The full blast-radius reduction pattern is in Reducing Renovate’s blast radius.

Each section addresses a specific supply chain concern.

Presets

config:recommended provides baseline behavior: dependency dashboard, semantic commit messages, grouped monorepo updates. helpers:pinGitHubActionDigests ensures future GitHub Actions updates include SHA pins. security:minimumReleaseAgeNpm sets the 3-day cooldown as a preset, which I override to 1 day at the root level.

rangeStrategy: "pin"

This is the setting that aligns Renovate with saveExact: true. Without it, Renovate preserves existing range formats. If package.json has "astro": "^6.4.0", Renovate writes "astro": "^6.5.0", reintroducing the caret. With "pin", Renovate writes "astro": "6.5.0", an exact pin.

The saveExact: true pnpm setting only affects what pnpm add writes. Renovate edits package.json directly and doesn’t go through pnpm add. Without rangeStrategy: "pin", the two paths produce different version formats.

minimumReleaseAge: "1 day"

Reduced from 14 days to 1 day to match pnpm’s minimumReleaseAge: 1440 (1 day in minutes). Renovate controls when it suggests updates; pnpm controls what gets installed (including transitive dependencies Renovate doesn’t manage). Matching values provide consistent coverage across direct and transitive dependencies.

The shorter window is acceptable because OSV scanning (osvVulnerabilityAlerts) now catches known malicious packages automatically2, and pnpm audit signatures in CI verifies provenance. These compensating controls make the tighter cooldown safe.

The Renovate docs recommend specifying minimum release age in both the bot and package manager configuration. Renovate and pnpm are independent; Renovate cannot read pnpm’s setting.

Alpha packages that need immediate updates are excluded from pnpm’s cooldown in pnpm-workspace.yaml via minimumReleaseAgeExclude. For this project, that means astro@7.0.0-alpha.2 and @astrojs/vue@7.0.0-alpha.0 are available immediately. This is a deliberate tradeoff: alpha releases of framework packages need fast turnaround when they break.

osvVulnerabilityAlerts: true

Integrates with the OSV database to detect known vulnerabilities and malicious packages in direct dependencies. Malicious updates are skipped automatically. The OSV feed includes OpenSSF Malicious Packages advisories, which in recent attacks provided alerts within 5 hours3.

SHA pin protection

{
  "description": "Do not update commit SHAs for reusable workflows within the same version tag",
  "matchFileNames": [".github/workflows/"],
  "matchUpdateTypes": ["pinDigest"],
  "enabled": false
}

This prevents Renovate from rotating the SHA inside a version tag comment. If actions/checkout@v6 is pinned to a specific SHA, Renovate won’t update the SHA without also bumping the version tag. This prevents the trivy-action attack pattern where a compromised tag gets its SHA silently rotated4.

Automerge rules

Non-major updates (minor, patch, digest) for stable packages (version >= 1.0.0) automerge after passing the cooldown. Pre-1.0 packages are excluded; unstable semver carries higher risk. Dev dependencies are grouped separately for cleaner PR history.

Schedule

"schedule": ["before 3am on Monday"]

Restricts Renovate to a single weekly window. Without a schedule, Renovate runs on every push and every repository event, opening PRs throughout the week. A weekly cadence batches updates into one review session and reduces notification noise.

engines and mise toolchain rules

{
  "description": "Never update engines; mise.toml is the source of truth for tool versions",
  "matchDepTypes": ["engines"],
  "enabled": false
},
{
  "description": "Group mise-managed toolchain updates together",
  "matchManagers": ["mise"],
  "groupName": "mise toolchain"
}

The engines disable rule prevents Renovate from bumping the engines field in package.json. mise.toml is the sole source of truth for tool versions; engines uses range guards (">=11.8.0 <=12") that don’t need bumping on every release. The mise manager groups all mise.toml tool bumps (node, pnpm, rust, python) into a single PR. See the Corepack drift TIL for why this separation matters.

CI: pnpm audit signatures

A one-liner added to the CI workflow:

- name: Verify package signatures
  run: pnpm audit signatures

This verifies ECDSA signatures against npm’s published keys. It catches packages published outside their trusted CI pipeline. For example, a maintainer’s account is compromised and they publish manually without provenance. The pnpm docs describe this as catching “packages published outside trusted pipelines.”

Exact pins everywhere

I stripped all ^ and ~ prefixes from package.json:

- "@astrojs/rss": "^4.0.0",
+ "@astrojs/rss": "4.0.18",

This was a one-time cleanup. Without it, existing floating ranges could resolve to versions that bypass Renovate’s cooldown. The rangeStrategy: "pin" setting ensures future Renovate updates also write exact versions.

The pnpm update gap

Running pnpm update --latest bypasses minimumReleaseAge5. The setting applies during pnpm install resolution, not during pnpm update. Renovate respects the cooldown; pnpm update does not.

For a supply chain hardened workflow, use Renovate for updates. pnpm update is a shortcut that skips the cooldown.

Remaining tradeoffs

I’d add OSV scanning and SHA pin protection at the same time as the initial Renovate setup. Adding them later meant backfilling controls onto a running pipeline. Configuring all three layers (cooldown, vulnerability scanning, provenance verification) together produces a tighter security posture from day one.

The one tradeoff: Renovate’s minimumReleaseAge only applies to direct dependencies6. Transitive dependencies depend on pnpm’s minimumReleaseAge alone. pnpm audit and pnpm audit signatures in CI are the safety net for those.

References

Footnotes

  1. The Shai-Hulud campaign was a coordinated attack on Red Hat cloud service packages. The attacker deployed malicious versions that stole cloud credentials from CI pipelines. Projects using automated dependency bots with auto-merge pulled the compromised versions within minutes of publication.

  2. The OSV database aggregates vulnerability reports including the OpenSSF Malicious Packages repository. Renovate’s osvVulnerabilityAlerts (documented here) queries OSV for known malicious packages and blocks automatic updates to compromised versions.

  3. Detection times vary. Some attacks are caught within hours (the Cloudsmith “Six Hours Too Late” analysis documents a July 2025 incident caught in roughly 6 hours). Others have remained active for days. The 5-hour figure reflects the best-case alerting from OpenSSF’s Malicious Packages feed, not a guarantee.

  4. The GitGuardian analysis documents the attack pattern: an attacker compromises a GitHub Actions tag, the tag gets silently rotated to a malicious SHA, and Renovate naively updates the SHA in lockstep with the compromised tag. Disabling pinDigest updates on GitHub Actions workflow files prevents this.

  5. This is documented in pnpm’s minimumReleaseAge settings. The setting operates during dependency resolution at install time. The pnpm update command performs a different resolution path that does not enforce this constraint.

  6. Renovate’s dependency management scope is limited to the manifests it manages. Transitive dependencies are the package manager’s responsibility, which is why pnpm’s minimumReleaseAge and CI audit steps are complementary controls.

This post was written with AI assistance.