Skip to content

fix(deps): clear 8 advisories, and pin the Node that writes the lockfile - #659

Merged
parthpahwa1 merged 2 commits into
devfrom
fix/advisory-lockfile-npm10
Sep 20, 2026
Merged

parthpahwa1 merged 2 commits into
devfrom
fix/advisory-lockfile-npm10

Conversation

@parthpahwa1

@parthpahwa1 parthpahwa1 commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

ci-frontend / build-and-audit is red on dev at npm audit --audit-level=high. This clears it — and then fixes the reason it happened.

The repair

Eight advisories published against already-pinned versions. Every move is a patch/minor inside the existing ranges, no manifest changes, no new direct dependency:

severity package issue
critical astro 7.2.0 → 7.3.3 RCE via AVIF image optimization
high sharp 0.35.3 → 0.35.4 libheif GHSA-g89c-p67h-r497 / -2jg2-4ch7-h545
high svgo 4.0.2 → 4.1.0 removeScripts sanitiser bypasses (×2)
high smol-toml 1.7.0 → 1.8.0 DoS via malformed TOML
high js-yaml 4.3.1 → 4.3.2 maxTotalMergeKeys does not bound CPU
high browserslist 4.28.6 → 4.29.0 unbounded cache growth → OOM
high fast-uri 3.1.5 → 3.1.8 host confusion via skipped IDN canonicalisation
high @xmldom/xmldom 0.8.13 → 0.8.15 XML fragment injection via EntityReference

The cause

Nothing in this repo says which Node and npm it is built with, so four of them touch one package-lock.json:

developer Mac Node 25 / npm 11.9 writes the lockfile
8 CI job steps Node "22" / npm 10.9 judge the lockfile
apps/frontend node:26-alpine installs from the lockfile
ci-cli (2 jobs) Node "20" one deliberate, one accidental

npm 10 and npm 11 do not agree on what a valid tree is. Measured here:

dev lockfile npm 11.9-edited lockfile
npm 10.9.8 (CI) npm ls exit 0 exit 1 — 7 extraneous, 3 invalid
npm 11.9.0 (local) exit 1 — 11 problems exit 1 — 10 problems

Under npm 11.9 both look broken, so a local check cannot tell a good lockfile from a bad one. That is why an earlier attempt at these same bumps concluded — on the Mac's npm — that a break it had just introduced was pre-existing.

The drift is already written into this repo as workarounds. ci-cli explains an unquoted glob with "this job runs Node 20 … the desktop workflow gets away with the quoted form only because it runs Node 22". release-cli opens with "Node 22, not the 20 the CI workflows use". Each is a note about which Node happens to be underneath.

The fix — say it once, enforce it, point everything at it

.nvmrc 22.23.2 — the version CI already resolves today
package.json engines the same, so npm can check it
.npmrc engine-strict=true a refusal, not an advisory warning
8 workflow steps node-version-file: ".nvmrc", no version of their own
apps/frontend/Dockerfile node:22.23.2-alpine

Same pattern the kaleidoscope-docs repo already uses for the same reason. On the wrong Node, npm now stops before writing anything:

npm error code EBADENGINE
npm error notsup Required: {"node":"22.23.2"}
npm error notsup Actual:   {"node":"v25.6.1","npm":"11.9.0"}

One deliberate exception, now stated in the file instead of implied: ci-cli's cli matrix job stays on Node 20, because its job is to prove the published launcher still runs on the oldest Node tools/cli claims to support (engines: >=20). It installs nothing from the root lockfile, and engine-strict gates installs only — verified npm run check, npm run smoke and the pack-manifest check all still pass there on Node 20.

Its neighbour packed-payload moves onto the pin: it runs npm ci at the repo root, so it was a builder wearing a floor-test's Node.

release-cli keeps its explicit npm install -g npm@^11.5.1 — trusted publishing needs OIDC, which npm 10 has not got. That upgrade only ever runs npm ci, which reads the lockfile and never rewrites it, so it cannot reintroduce the drift.

Verification

On node 22.23.2 / npm 10.9.8, the pinned pair:

  • npm ci clean · npm audit --audit-level=high passes ← the step dev fails on
  • npm ls --json --long --all exits 0 · SBOM builds (valid CycloneDX 1.6)
  • six package typechecks at 0 errors · frontend and website build
  • playwright version check passes · desktop-runtime tests pass

On Node 20: the cli floor job passes. On Node 25: npm refuses — which is the point.

The lockfile carries the root engines too, so it is regenerated in step; without that, manifest and lockfile disagree quietly and npm ci still passes.

The advisory half of this is already green across all 14 checks on #658, which carries it so that PR could pass while dev was red.

🤖 Generated with Claude Code

…udit` on dev

`ci-frontend` is the only red workflow on dev, and has been since 7193fbc. It
fails at `npm audit --audit-level=high` — not on a code change, but on
advisories published against versions already pinned. The gate ignores the
moderates, so these 8 are what matter:

  critical  astro             <=7.2.7      RCE via AVIF image optimization
  high      sharp             <0.35.4      libheif GHSA-g89c-p67h-r497 / -2jg2-4ch7-h545
  high      svgo              4.0.0-4.0.2  removeScripts sanitiser bypasses (x2)
  high      smol-toml         <=1.7.0      DoS via malformed TOML
  high      js-yaml           4.0.0-4.3.1  maxTotalMergeKeys does not bound CPU
  high      browserslist      <=4.28.6     unbounded cache growth -> OOM
  high      fast-uri          3.0.0-3.1.5  host confusion via skipped IDN canonicalisation
  high      @xmldom/xmldom    <=0.8.14     XML fragment injection via EntityReference

Every move is a patch/minor INSIDE the existing ranges: astro 7.2.0 -> 7.3.3
(satisfies ^7.2.0), sharp 0.35.3 -> 0.35.4, svgo 4.0.2 -> 4.1.0, smol-toml
1.7.0 -> 1.8.0, js-yaml 4.3.1 -> 4.3.2, browserslist 4.28.6 -> 4.29.0,
fast-uri 3.1.5 -> 3.1.8, xmldom 0.8.13 -> 0.8.15. No manifest changes and no
new direct dependency; only package-lock.json moves and every package.json is
byte-identical. It is all the apps/website (Astro) tree — nothing in
apps/desktop or packages/* moves.

BUILD IT ON THE NPM CI ACTUALLY RUNS. The workflow pins node 22, which brings
npm 10.9.8, and that pairing is what the lockfile has to satisfy. Two traps:

1. npm 10.9.8 cannot run `npm audit fix` in this repo at all — it dies on
   `"playwright": "$playwright"` in the root overrides with `Unable to resolve
   reference $playwright`. Plain `npm install --package-lock-only` and
   `npm update` are unaffected, so the bumps are done with those. Worth
   replacing that `$playwright` reference with its literal version if we want
   `audit fix` back.

2. Reaching for a newer npm to get around (1) produces a lockfile that reds the
   NEXT gate. npm 11.9's resolver nests un-overridden copies of
   @electron/asar@3.4.1, @electron/universal@2.0.3 and ejs@3.1.10 under
   app-builder-lib — the exact pre-override versions the root `overrides` block
   exists to eliminate — and strands the @img/sharp-wasm32 subtree that sharp
   0.35.4 stops referencing. npm 10.9.8 then reports 3 invalid and 7 extraneous
   from `npm ls`, which is what `cyclonedx-npm` shells out to, so the SBOM step
   fails. Measured both ways on this repo:

                           dev lockfile        npm 11.9-built lockfile
     npm 10.9.8 (CI)       npm ls exit 0       exit 1 — 7 extraneous, 3 invalid
     npm 11.9.0 (local)    exit 1 — 11 probs   exit 1 — 10 probs

   Under npm 11.9 both look broken, so a local check cannot tell them apart.
   Under CI's npm only the bad one fails.

Verified on node 22.23.2 / npm 10.9.8, the workflow's pinned pair: `npm ci`
clean, `npm audit --audit-level=high` passes, `npm ls --json --long --all`
exits 0, the SBOM step produces a valid CycloneDX 1.6 document, all six package
typechecks at 0 errors, frontend builds, and the website — the only tree that
moved — builds on the bumped Astro.

The same lockfile is already green across all 14 checks on #658, which carries
it so that PR could pass while dev was red. This lands it on dev directly so it
stops depending on unrelated feature work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s one thing

The red `build-and-audit` this branch already repairs was a symptom. The cause
is that nothing here says which Node and npm the repo is built with, so four of
them touch one `package-lock.json`:

  developer Mac      Node 25 / npm 11.9   WRITES the lockfile
  8 CI job steps     Node "22" / npm 10.9 JUDGE the lockfile
  apps/frontend      node:26-alpine       INSTALLS from the lockfile
  ci-cli (2 jobs)    Node "20"            one deliberate, one accidental

npm 10 and npm 11 do not agree on what a valid tree is. Measured on this repo:
under npm 10.9.8 the dev lockfile passes `npm ls` and an npm-11.9-edited one
fails with 7 extraneous and 3 invalid; under npm 11.9 BOTH look broken, so a
local check cannot tell a good lockfile from a bad one. That is the whole
failure, and it is why the earlier deps commit concluded — on the Mac's npm —
that a break it had just introduced was pre-existing.

The same drift is already written into this repo as workarounds. `ci-cli`
explains an unquoted glob with "this job runs Node 20 ... the desktop workflow
gets away with the quoted form only because it runs Node 22". `release-cli`
opens with "Node 22, not the 20 the CI workflows use". Each is a note about
which Node happens to be underneath.

So: say it once, enforce it, and point everything at it.

  .nvmrc                  22.23.2 — the version CI already resolves today
  package.json engines    the same, so npm can check it
  .npmrc engine-strict    a refusal, not an advisory warning
  8 workflow steps        node-version-file: .nvmrc, no version of their own
  apps/frontend           node:22.23.2-alpine

This is the pattern the kaleidoscope-docs repo already uses for the same
reason. On the wrong Node, npm now stops before writing anything:

  npm error code EBADENGINE
  npm error notsup Required: {"node":"22.23.2"}
  npm error notsup Actual:   {"node":"v25.6.1","npm":"11.9.0"}

ONE DELIBERATE EXCEPTION, now stated in the file instead of implied: ci-cli's
`cli` matrix job stays on Node 20, because its job is to prove the published
launcher still runs on the oldest Node `tools/cli` claims to support
(`engines: >=20`). It installs nothing from the root lockfile, and `engine-strict`
gates installs only — verified that `npm run check`, `npm run smoke` and the
pack-manifest check all still pass there on Node 20.

That job's neighbour, `packed-payload`, moves ONTO the pin: it runs `npm ci` at
the repo root, so it was a builder wearing a floor-test's Node. Its glob comment
goes with it, having described a Node 20 quirk it no longer runs into.

`release-cli` keeps its explicit `npm install -g npm@^11.5.1`: trusted
publishing needs OIDC, which npm 10 has not got. That upgrade only ever runs
`npm ci`, which reads the lockfile and never rewrites it, so it cannot
reintroduce the drift.

The lockfile carries the root `engines` too, so it is regenerated in step —
without that, manifest and lockfile disagree quietly and `npm ci` still passes.

Verified on node 22.23.2 / npm 10.9.8: `npm ci` clean, `npm audit
--audit-level=high` passes, `npm ls --json --long --all` exits 0, SBOM builds,
six typechecks at 0 errors, frontend and website build, playwright version check
passes, desktop-runtime tests pass. On Node 20: the cli floor job passes. On
Node 25: npm refuses, which is the point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@parthpahwa1 parthpahwa1 changed the title fix(deps): clear the 8 high/critical advisories that red build-and-audit on dev fix(deps): clear 8 advisories, and pin the Node that writes the lockfile Sep 20, 2026
@parthpahwa1
parthpahwa1 merged commit f819d44 into dev Sep 20, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants