Skip to main content

Dependencies

Renovate for updates, a 24-hour quarantine for new versions, an install-script allowlist, and a CI audit that blocks only advisories users would install.

Most supply-chain attacks on npm are caught and pulled within hours. We do not install a version until it is a day old, we do not run install scripts we did not approve, and publishing never runs dependency code. All of this is built into pnpm and Renovate; there are no custom dependency scripts.

Quarantine

pnpm-workspace.yaml:

yaml
minimumReleaseAge: 1440       # minutes: new versions wait 24 hours
minimumReleaseAgeStrict: true # fail instead of quietly excluding too-young versions
allowBuilds:                  # the only dependencies allowed to run install scripts
  esbuild: true

pnpm refuses to resolve a version younger than 24 hours, including transitive ones. With minimumReleaseAgeStrict off, pnpm would instead add such a version to minimumReleaseAgeExclude by itself and install it; strict mode fails in CI and asks at the terminal. pnpm 11 turns strict mode on whenever minimumReleaseAge is set; the starters write it out so nobody turns it off by accident. Only list packages in allowBuilds (onlyBuiltDependencies on pnpm 10) that really need a build step. Never set dangerouslyAllowAllBuilds.

Pin pnpm in package.json ("packageManager": "pnpm@11.x.y"). An older pnpm can silently ignore these settings.

The quarantine applies to third-party versions only. Our own packages skip it, so a fix in one Lupinum library reaches the others the same day:

yaml
minimumReleaseAgeExclude:
  - '@lupinum/*' # our own scope; published only by our protected workflows

This is the one standing exclusion, decided on 2026-10-07 while the Ginko libraries release often. Review it when release frequency drops.

Updates

Renovate opens update pull requests. .github/renovate.json:

json
{
  "extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
  "minimumReleaseAge": "1 day",
  "internalChecksFilter": "strict",
  "rangeStrategy": "replace",
  "schedule": ["before 6am on Monday"]
}

Renovate waits the same day before it proposes a version, and keeps GitHub Actions pinned to full commit SHAs with a version comment. replace keeps the ranges that consumers of a library see; a repository that publishes nothing may use pin. The weekly schedule batches updates into one review session. Renovate opens security updates outside the schedule; pnpm still makes the new version wait 24 hours unless you exclude it (see the last section). Dependabot stays on for security alerts only; its security updates are off (see Set up a repository), so only Renovate opens update pull requests.

When an update changes dependencies or peerDependencies of a published package (not only the lockfile), users get the change with the next version. Such a pull request needs a changeset that bumps that package, at least patch, for example "Change the foo dependency to v2."; CI fails without one. Push it to the Renovate branch. Dev dependencies and lockfile-only updates need no changeset.

An agent may merge a green Renovate pull request for a patch or minor update. A major update, or an update to anything that runs in CI or at build time (bundlers, test runners, GitHub Actions), gets a quick read of the release notes first.

Audit

An advisory blocks only when users would install the vulnerable code. CI runs node scripts/audit-deps.mjs as part of pnpm verify. It runs pnpm audit --prod and sorts every finding by where it comes from:

FindingResult
High or critical, reached through the production dependencies of a published packageCI fails
Anything in the docs site, in dev tools, or below highA warning in the CI log; Renovate fixes it on its normal schedule

The docs site and the test tools never reach users, so an advisory there must not block every pull request and release. Dependabot alerts stay on as notifications; the audit lists them as advice (OPS-03), and CI decides what blocks. When audit-deps.mjs fails:

  1. Update the affected package, usually through the open Renovate pull request.
  2. If no fixed version exists, check whether the vulnerable code is reachable from the library. If it is not, ignore that one advisory with a reason and a date to look again:

    yaml
    # pnpm-workspace.yaml
    auditConfig:
      ignoreGhsas:
        - GHSA-xxxx-xxxx-xxxx # not reachable from our code; recheck 2026-11-01
  3. If it is reachable, treat it as a security report.

Only ignore an advisory that blocks. A warning needs no entry.

When the quarantine blocks something you need now

First try an older version that has the fix. If that is not possible, for an urgent security fix only, exclude one exact version:

yaml
minimumReleaseAgeExclude:
  - some-package@1.2.3 # urgent fix for GHSA-…; remove after 2026-10-02

Remove the line once the version is a day old. The audit reports every exclusion except @lupinum/* as a warning until you do. Never exclude a range or a whole third-party package.