diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d9371e..e572845 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,8 +176,13 @@ jobs: # Production deps are the blocking gate (step above). Dev-only transitive # advisories (e.g. tmp <0.2.6 path traversal, build-time tooling) are # reported for visibility but must not block CI — they ship to no user. + # No --audit-level here on purpose: this step cannot fail the build, so a + # severity floor buys nothing and only hides findings. It used to inherit + # `high`, which is why the medium uuid advisory (GHSA-w5hq-g745-h8pq) was + # invisible to CI until someone read the Dependabot list by hand — and + # transitive advisories have no bot, so this step is the discovery lane. - name: Audit all dependencies (non-blocking) - run: pnpm audit --audit-level=high + run: pnpm audit continue-on-error: true - name: Install cargo-audit diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b3eb61..483831c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,11 @@ If you need stable, depend on a published version on npm. ## Dependency updates -**Renovate is the only bot that opens dependency PRs here** (config: `renovate.json`, extending `cachekit-io/renovate-config`). It also handles vulnerability remediation for direct dependencies; vulnerable _transitives_ are floor-pinned via `overrides` in `pnpm-workspace.yaml`. Dependabot's security-update PRs are disabled — its _alert feed_ stays on and feeds Renovate. +**Renovate is the only bot that opens dependency PRs here** (config: `renovate.json`, extending `cachekit-io/renovate-config`). It also handles vulnerability remediation, but **only for dependencies that appear in a manifest** — `osvVulnerabilityAlerts` is documented as direct-dependencies-only, and Renovate removed transitive remediation outright in [renovatebot/renovate#27985](https://github.com/renovatebot/renovate/pull/27985). + +**Vulnerable transitives have no bot.** Find them with `pnpm audit`, then floor-pin them in `overrides` in `pnpm-workspace.yaml` — bounded to the major of the version you pin, because an unbounded floor re-resolves into new majors. Maintainers additionally watch the repo's Dependabot alerts (that page needs write access, so `pnpm audit` is the check to run from a fork). + +Two traps in that loop. Pinning hands the dependency _back_ to Renovate — an `overrides` entry reads as a manifest dep — but Renovate will also propose **widening** an upper bound across majors, as open PR #94 does to `brace-expansion@2`. An upper bound here is a deliberate decision; re-derive it before approving a bot PR that moves one. And Dependabot's _alert feed_, though still enabled, is not a Renovate input: as of 2026-07-30 the acting Renovate App lacks `vulnerability_alerts: read`, which is what the `Cannot access vulnerability alerts` warning on every Dependency Dashboard reports. Granting that scope would silence the warning and let Renovate prioritise advisories on deps it can already see — it would not produce transitive fix PRs, because that capability is gone upstream regardless. **Release-age quarantine**: `pnpm-workspace.yaml` pins `minimumReleaseAge: 1440` (24 h). `pnpm install --frozen-lockfile` in CI rejects any lockfile entry younger than that — so a lockfile refresh that picks up a just-published version will fail CI until the release ages past the window. pnpm applies the same window when resolving, so a plain `pnpm install` on your machine normally picks mature versions automatically. Two cases still fail locally: a lockfile generated by a tool that ignores the window, and a range with **no** aged-in candidate at all — if every version satisfying a dependency (or an `overrides` floor) is younger than 24 h, resolution has nothing legal to pick and errors out rather than falling back. For a security backport that can't wait out the window — the usual cause of the second case — add a _version-scoped_ entry to `minimumReleaseAgeExclude` with a comment saying when it can be removed. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 464e27b..17e9551 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,7 @@ overrides: brace-expansion@5: '>=5.0.8' js-yaml: '>=4.3.0 <5' sharp: '>=0.35.0' + uuid: '>=11.1.1 <12' importers: @@ -2528,9 +2529,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true vite@8.1.0: @@ -4095,7 +4095,7 @@ snapshots: docker-modem: 5.0.7 protobufjs: 7.6.5 tar-fs: 2.1.4 - uuid: 10.0.0 + uuid: 11.1.1 transitivePeerDependencies: - supports-color @@ -4933,7 +4933,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@10.0.0: {} + uuid@11.1.1: {} vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(yaml@2.9.0): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 218f215..de75fa4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -51,8 +51,13 @@ allowBuilds: sharp: false # Security floor-pins for vulnerable transitives (formerly pnpm.overrides). -# Floors are bounded to their current major where a newer major exists — -# an unbounded floor re-resolves to the newest match, majors included. +# Floors are bounded to the major of the version they pin, where a newer major +# exists — an unbounded floor re-resolves to the newest match, majors included. +# Renovate takes these entries over once they exist (they read as manifest deps) +# but its idea of maintenance includes WIDENING the bound across majors: see the +# open PR #94, which proposes 'brace-expansion@2': '>=2.1.3 <3' -> '<6'. Review +# every bot PR that touches an upper bound here — a bound is a decision, not +# drift. overrides: protobufjs: '>=7.6.5 <8' # GHSA-j3f2-48v5-ccww vite: '>=8.0.5' @@ -65,3 +70,13 @@ overrides: 'brace-expansion@5': '>=5.0.8' # GHSA-3jxr-9vmj-r5cp + GHSA-mh99-v99m-4gvg js-yaml: '>=4.3.0 <5' # GHSA-52cp-r559-cp3m sharp: '>=0.35.0' # GHSA-f88m-g3jw-g9cj (build denied above; pin is belt+braces) + # GHSA-w5hq-g745-h8pq. Dev-only: testcontainers -> dockerode -> uuid, v4 only + # (alert #47 mislabels it runtime — it can't read pnpm-lock's dev partition). + # 11.1.1 is the advisory's lowest fix, and 10.0.0 is uuid's ONLY 10.x release + # (dockerode's declared range is ^10.0.0, which resolves to it), so there is + # no in-major fix and the forced major is unavoidable. + # <12 IS LOAD-BEARING: uuid >=12 dropped the + # `require` export condition and dockerode does `require("uuid").v4`, so + # widening breaks Node <22.12 — which engines allow (>=22.0.0) and CI, on + # 22-latest, would not catch. Lift only when dockerode moves off ^10.0.0. + uuid: '>=11.1.1 <12'