From dabf48ec25c2fc354b0e47b408d7aedb7784a788 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Thu, 30 Jul 2026 07:51:25 +1000 Subject: [PATCH 1/2] fix(deps): floor-pin uuid transitive, name the real owner, unblind the audit lane (LAB-1150) GHSA-w5hq-g745-h8pq (uuid < 11.1.1) had no owner. LAB-1020 punted it as "Renovate's job", and the Dependency Dashboard's "Cannot access vulnerability alerts" WARN made that look like a permission problem. It is not: Renovate DELETED transitive remediation in renovatebot/renovate#27985 (2024-04-14, "only works for deprecated npm versions"), and the surviving osvVulnerabilityAlerts engine is documented direct-dependencies-only. No grant of vulnerability_alerts:read could ever have produced a uuid PR. Transitive advisories on this repo have no bot at all -- that is the finding, and the permission gap is a separate, smaller one. Pin rather than accept-with-reason: an `overrides` entry reads as a manifest dep, which is the only thing that puts a transitive in front of Renovate at all. It also clears an EOL package -- uuid@10's own lockfile entry carried `deprecated: uuid@10 and below is no longer supported ... For CommonJS codebases, use uuid@11`. `<12` is load-bearing, not blast-radius hygiene. uuid >=12 dropped the `require` export condition (11.1.1 still ships `main` + a `require` branch); dockerode does `require("uuid").v4` at module scope, so widening past `<12` breaks `require()` on Node below 22.12. `engines` allows >=22.0.0 while CI runs 22-latest, so CI would stay green while contributors broke. 11.1.1 is the advisory's lowest fix and 10.0.0 is the entire 10.x line -- there was no backport, so the forced major is unavoidable. Exposure was nil, recorded so nobody re-panics: uuid reaches the graph only as testcontainers -> dockerode -> uuid, testcontainers is a devDependency of packages/cachekit, and dockerode calls only uuid.v4(). The advisory is a missing bounds check in v3/v5/v6 when the caller supplies `buf`. Dependabot alert #47 labels it runtime scope, which is wrong -- it cannot read pnpm-lock's dev partition. Two things review turned up that are worth more than the pin itself. The non-blocking all-deps audit ran `pnpm audit --audit-level=high` under `continue-on-error: true`. A severity floor on a step that cannot fail the build is not a control, it is a blindfold -- and `high` filtered out exactly the medium band this advisory sat in. So the one automated lane that could have found uuid was configured not to. Floor removed; it reports nothing new today, which is the point. Renovate does take these entries over once they exist, but its idea of maintenance includes WIDENING the upper bound across majors: open PR #94 proposes 'brace-expansion@2': '>=2.1.3 <3' -> '<6'. For uuid the equivalent PR would resolve uuid 14 and break the integration lane per the paragraph above. That is a live, unowned problem affecting all 12 pins, not just this one; a `renovate.json` packageRules fix is deliberately NOT folded in here, because it changes bot behaviour for every entry and belongs in its own reviewed change. Both files now warn the human who reviews such a PR. CONTRIBUTING.md said the Dependabot alert feed "feeds Renovate". It does not. Corrected, along with a "bounded to the current major" rule that 8 of the 12 existing entries contradict and that this pin itself contradicts (current major was 10). The Dependabot alerts page needs write access, so `pnpm audit` is now named as the check a fork can actually run. --- .github/workflows/ci.yml | 7 ++++++- CONTRIBUTING.md | 6 +++++- pnpm-lock.yaml | 10 +++++----- pnpm-workspace.yaml | 17 +++++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) 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..3407988 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,11 @@ 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 the entire 10.x line, so + # 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' From feb6ffe02225ff09f51990190fce37b9420e4786 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Thu, 30 Jul 2026 10:17:17 +1000 Subject: [PATCH 2/2] docs(deps): disambiguate uuid 10.0.0 as the only 10.x release vs dockerode's ^10.0.0 range CodeRabbit flagged the override rationale as ambiguous: bare '10.0.0' read as either uuid's resolved version or dockerode's declared range. Verified against the registry - uuid published exactly one 10.x release (10.0.0), so there is no in-major fix to move to and the forced major bump stands. Comment only; the pin and its load-bearing <12 bound are unchanged. --- pnpm-workspace.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3407988..de75fa4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -72,8 +72,10 @@ overrides: 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 the entire 10.x line, so - # the forced major is unavoidable. <12 IS LOAD-BEARING: uuid >=12 dropped the + # 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.