Skip to content

Repository files navigation

supply-chain

License: MIT Renovate enabled Ecosystems

Shared supply-chain policy for dependency updates

Multi-ecosystem • Multi-forge • Review-gated by default

Česká verze


What this is

A Renovate preset that encodes one decision: which dependency updates are allowed to land without a human looking at them, and which are not.

The answer is deliberately narrow. Trusted dev tooling and minor/patch CI action bumps automerge. Production dependencies, lockfile refreshes and bare digest moves do not — those are the paths a supply-chain attack travels.

It is not a JavaScript config. It covers Rust, Nix, Terraform, Ansible, Docker and CI actions alongside the JS ecosystems, because the threat model does not care what language you write.

Why it exists

The Shai-Hulud 2.0 npm attack in November 2025 is the origin story, not the scope. It prompted the first version and the 428-package watch list, but the policy here is general: assume any dependency can turn hostile between one release and the next, and make the blast radius a review instead of a merge.

The watch list still ships, gated behind dashboard approval. Affected version ranges are published in the Datadog IOC database.

Shared preset

🎯 Features

  • 7-day minimumReleaseAge, set as a packageRule for npm so it actually applies — a top-level value is outranked by the inherited npm rule
  • security:minimumReleaseAgeNpm — avoids freshly published and unpublished packages
  • No automerge for production deps — only trusted dev tooling automerges
  • Groups all non-major updates into one PR, majors stay separate
  • Uses Platform Automerge (GitHub Native) for faster merging of approved PRs
  • Automerges only trusted dev tooling (Biome, Oxlint, TypeScript, Vitest, Jest, ESLint, Prettier), matched on anchored names so neighbouring packages in unowned npm prefixes cannot inherit the trust
  • Automatic deduplication for npm/pnpm/yarn lockfiles
  • Weekly lock file maintenance, review required (minimumReleaseAge does not gate lockfile refreshes)
  • Semantic commits enabled (chore(deps): update package)
  • Vulnerability alerts with security labels
  • Pins GitHub Actions to digests; automerges minor/patch only, never a bare digest move
  • 428 known-compromised packages gated behind dashboard approval
  • Supports npm, pnpm, yarn, Bun, Deno, Rust, Nix, Terraform, Ansible, Docker, GitHub Actions

🛠️ Supported Ecosystems

Category Grouped as Managers
JavaScript / TypeScript (base) npm (covers npm, pnpm, yarn), bun, bun-version
Deno Deno deno
Rust Rust cargo
Go Go gomod
Python Python pip_requirements, pip-compile, poetry, pep621, pipenv, setup-cfg
JVM JVM gradle, gradle-wrapper, maven, maven-wrapper, sbt
.NET .NET nuget
PHP PHP composer
Ruby Ruby bundler
Dart / Flutter Dart pub
Swift Swift swift, cocoapods
Infrastructure Nix / Terraform / Ansible nix, terraform, ansible
Containers Docker digests docker
Kubernetes Kubernetes kubernetes, helmv3, helm-values, helm-requirements, helmfile, flux, argocd
CI/CD CI: GitHub Actions github-actions

Anything not listed still gets updates — everything non-major groups into one PR by default, and the rules above only split specific ecosystems back out.

How to use

Drop this file into a new repo and you are done:

{ "extends": ["github>ownctrl/supply-chain"] }

That is the whole setup. The preset carries the schedule, grouping, automerge policy and ecosystem coverage — there is nothing else to configure per repo.

Two prerequisites, both one-time:

  1. Renovate reaches the repo. On GitHub.com that is the Mend Renovate App, installed for the account or org. On every other forge you run Renovate yourself — see below.
  2. Allow auto-merge is enabled in the repo's settings. This preset sets platformAutomerge, which uses GitHub's native auto-merge. With the repo setting off, Renovate silently falls back to merging through its own bot instead — it still works, but not the way this preset describes.

Where Renovate runs

The preset is only JSON; it works wherever Renovate does. Getting Renovate to run is the part that differs, and only GitHub.com is free of setup:

Forge How Renovate runs
GitHub.com Mend Renovate App — hosted, free, nothing to run
GitLab self-hosted; the renovate-runner CI template runs it as a scheduled pipeline
Codeberg / Forgejo / Gitea self-hosted; run the Renovate CLI on a schedule
Bitbucket self-hosted

This is a property of the Renovate ecosystem, not of this preset — you would face it with any preset, or with none. Mirroring this repo to another forge is about making the preset resolvable there; nothing runs on the mirror itself.

What to expect on a fresh repo

  • Nothing happens until Monday. The schedule is before 06:00 on monday (Europe/Prague). This is not a misconfiguration — set "schedule": ["at any time"] in your repo if you want the first run immediately.
  • The Dependency Dashboard issue is the control surface. Majors and known-compromised packages wait there for a click.
  • What automerges on its own: trusted dev tooling (Biome, Oxlint, TypeScript, Vitest, Jest, ESLint, Prettier and their scopes) and minor/patch GitHub Action bumps. Everything else opens a PR and waits for you.

That last point is the deliberate trade-off: production dependencies, lockfile refreshes and bare digest moves are the paths a supply-chain attack travels, so they are review-gated by design. Expect a handful of clicks a week, not zero.

github> and when to use local> instead

github> names the forge explicitly and is the right default today, because GitHub is where this repo actually lives.

local> resolves against whichever forge Renovate is currently running on, so one line would work on GitHub, GitLab, Codeberg and self-hosted Forgejo alike — but only once the preset is mirrored to each of them under the same path. It is not, yet. Reach for local> when you maintain your own mirrored copy.

Pin a release if you do not want your policy to change under you:

{ "extends": ["github>ownctrl/supply-chain#v1.0.0"] }

Three ways to adopt this, and what each costs

Just extend it. Fixes reach you the moment they land. Nothing to maintain.

{ "extends": ["github>ownctrl/supply-chain"] }

Extend it and override. Same, plus your own additions. Use this for a second brand or team rather than forking — one security fix should not have to be applied once per copy.

{
  "extends": ["github>ownctrl/supply-chain"],
  "labels": ["dependencies", "yourbrand"]
}

Take a copy. This repo is a GitHub template. Use it when you need to own the policy outright — an air-gapped mirror, a compliance requirement, a disagreement with a decision here.

Be clear about the price: a copy stops receiving fixes. Four faults were found in this preset that no amount of reading the documentation would have surfaced, and a copy taken before each one kept it. For a security policy that is the expensive direction.

If you want your own address and the fixes, have your copy extend upstream:

{
  "extends": ["github>ownctrl/supply-chain"],
  "packageRules": [ /* your deviations, and only those */ ]
}

The Setup Owner workflow rewrites the preset references and LICENSE to the new owner when you dispatch it manually.

JavaScript runtimes and package managers

Renovate has three managers here, and the split is not the one you would guess:

  • npm covers npm, pnpm and yarn. There is no separate pnpm or yarn manager — all three are the same manager reading different lockfiles.
  • bun is its own manager (bun.lock, bun.lockb). Commit the lockfile.
  • deno is its own manager (deno.json, deno.jsonc, deno.lock) and pulls from the npm, jsr and deno datasources.

.bun-version is picked up by the bun-version manager.

Nub needs nothing special. It reads and writes whichever lockfile the project already has (package-lock.json, pnpm-lock.yaml, bun.lock), so Renovate keeps using the matching manager and nub reads the result. Note that lockfile refreshes are performed by the incumbent package manager, not by nub.

Biome & Oxlint

  • Biome (@biomejs/*) is treated as trusted dev tooling and grouped + automerged on non-major updates.
  • Oxlint (oxlint, @oxc-project/*) follows the same pattern as Biome.

Nix & NixOS

  • Nix Flakes are supported via Renovate's nix manager. Commit your flake.lock file for reliable updates.
  • Nix dependencies are grouped together with higher priority (prPriority: 5).
  • Renovate will automatically update inputs in your flake.lock when new versions are available.

Terraform & Ansible

  • Terraform modules and providers are managed via the terraform manager. Works with main.tf, versions.tf, and other Terraform files.
  • Ansible Galaxy roles and collections are supported via the ansible manager (looks for requirements.yml or galaxy.yml).
  • Both are grouped separately with higher priority (prPriority: 5) for infrastructure changes.

Socket.dev & Dependabot Compatibility

  • Socket.dev works perfectly with Renovate - they complement each other. Socket provides supply chain security scanning, while Renovate handles updates. Socket can block problematic PRs from Renovate.
  • Dependabot Alerts - keep them enabled for security notifications. Remove .github/dependabot.yml if you used Dependabot "version updates" to avoid duplicate PRs.

Policy summary

Setting Value Reason
minimumReleaseAge 7 days Avoid freshly published packages (set as a packageRule for npm, which outranks the top-level value)
security:minimumReleaseAgeNpm enabled Avoid freshly published and unpublished packages
rangeStrategy pin Lock exact versions (npm, bun, deno)
prConcurrentLimit 4 Avoid PR storms
schedule Mondays 06:00 Weekly updates
timezone Europe/Prague Local timezone
automerge (prod deps) ❌ disabled Security review required
automerge (trusted dev) ✅ enabled Biome, TypeScript, Vitest, etc.
vulnerabilityAlerts ✅ enabled With security labels
lockFileMaintenance ✅ weekly Review required — the age gate does not apply here

Validating locally

./tooling/validate.sh

Same list of presets CI checks. Wire it up as a pre-push hook if you want it to run on its own:

ln -s ../../tooling/validate.sh .git/hooks/pre-push

A local hook is a convenience, not a boundary — anyone can skip it. CI stays the real gate.

What the gate checks

validate.sh runs two things, and the second matters more:

  • renovate-config-validator --strict — the presets are well formed
  • tooling/test_policy.py — the presets decide what they should

The validator checks shape, not meaning. Every fault this preset has shipped passed it: an unanchored pattern granting trust to a namespace nobody owns, a rule ordered so it undid the one above it, two manager names that do not exist. The policy test freezes those as cases, and fails if lockfile maintenance ever gets automerge back.

It reimplements Renovate's matching rather than calling Renovate, so it can drift from the real engine. A failure is a reason to look; a pass is weaker evidence than a dry run.

Testing locally

You can test this config locally before deploying:

# Install Renovate CLI
npm install -g renovate

# Run in dry-run mode (no changes made)
LOG_LEVEL=debug renovate --platform=local --dry-run=true

# Or use npx without installing
npx renovate --platform=local --dry-run=true

Presets

Each of these is a ready preset, not a snippet to copy. Reference it directly:

Preset Reference What it changes
base github>ownctrl/supply-chain the policy described above
lockdown github>ownctrl/supply-chain:lockdown nothing automerges, 14-day npm floor, every update waits for dashboard approval
no-automerge github>ownctrl/supply-chain:no-automerge automerge off, everything else unchanged
aggressive github>ownctrl/supply-chain:aggressive any time, no release-age floor, higher PR limit

Reach for lockdown during an active supply-chain incident and aggressive only when you are certain there is not one.

{ "extends": ["github>ownctrl/supply-chain:lockdown"] }

Sub-presets extend the base themselves, so you do not list both.

Custom timezone

For teams in different timezones:

{
  "extends": ["github>ownctrl/supply-chain"],
  "timezone": "America/New_York",
  "schedule": ["before 09:00 on monday"]
}

Shai-Hulud Affected Packages

This preset includes warnings for packages affected by the Shai-Hulud 2.0 attack. When Renovate proposes updates for these packages, the PR will include:

  • ⚠️ Security warning banner
  • Checklist for verification
  • Links to IOC lists

Currently monitored packages: 428

Sourced from the Datadog IOC database. These are gated behind dashboard approval with a warning attached — not blocked, so fixed versions can still land.

The full list lives in default.json. Affected version ranges are in the Datadog IOC database.


What this does not do

This preset decides which dependency updates may merge unattended. That is its whole scope, and it is worth being blunt about the edges:

  • It gates updates, not what you already have. A hostile version already in your lockfile is outside its reach. Check the lockfile directly and rotate anything the package could have read.
  • It is not a scanner. It does not inspect package contents, verify that a published artifact matches its source, or detect compromise. Pair it with something that does.
  • A delay is not a guarantee. minimumReleaseAge buys time for someone else to notice a bad release. Nobody may notice. Shai-Hulud went unnoticed for longer than seven days in some packages.
  • The watch list is a snapshot. 428 packages known compromised in one attack, in November 2025. It says nothing about the next one.
  • The policy test reimplements Renovate's matching rather than calling Renovate, so it can drift from the real engine.

Provided as is, without warranty, under the MIT licence. Deciding what your project may merge unattended is your decision; this preset is a starting point with reasoning attached, not a substitute for making it.

The Setup Owner workflow

.github/workflows/setup-owner.yml exists for the copy path. Dispatch it manually after taking a copy and it rewrites the preset references and the LICENSE copyright to whoever now owns the repo, then deletes itself.

It runs with contents: write and pushes to the default branch, which is what it needs to do that job. It is workflow_dispatch only — nothing triggers it automatically. If you are extending this preset rather than copying it, you will never run it.

Versioning

main is what an unpinned github>ownctrl/supply-chain resolves to, so every merge reaches those consumers immediately. Tags exist for the people who pinned. Merge as often as you like; cut a tag when a pinned consumer has a reason to move.

There is no API here to version. What a consumer gets from this preset is one thing — what merges without them looking — so that is what the number tracks.

Meaning Example
MAJOR something now merges unattended that did not before, or an existing config breaks adding a package to the trusted list; renaming a sub-preset
MINOR new coverage that loosens nothing a new ecosystem group, a new sub-preset, more packages on the watch list
PATCH nothing about what merges changes documentation, PR body text, our own pinned tooling

The asymmetry is deliberate. Tightening is minor, loosening is major, even when the diff is the same size. If something stops automerging, the worst case is a few more clicks. If something starts automerging, you have gone around the reason someone pinned in the first place — a pin is a promise that the posture will not shift underneath them.

So adding one package to the trusted list is a major, one line or not.

Related Resources

Scroll Top

Maintained by @miccy © 2026 ownCTRL™

About

Shared supply-chain policy for dependency updates. Renovate preset covering JS/TS, Rust, Nix, Go, Python, Docker, Terraform and CI actions — review-gated by default.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages