# Dependency overrides

Every npm dependency in this repo is **development tooling**. There is no `src/`,
no `block.json` and no webpack config, so `wp-scripts build` compiles nothing;
`node_modules`, `package.json` and `package-lock.json` are all excluded from the
release zip by `.distignore`. Nothing here reaches production or a user — npm's
only real jobs in this project are husky + commitlint for the commit-message
hook.

That means these advisories carry no runtime risk. They are still worth closing:
the tooling runs on developer machines and in CI, and an empty `npm audit` is
what keeps the Dependabot queue from refilling with one PR per transitive
package (it previously held 22).

The `overrides` block in `package.json` exists because a handful of transitive
pins inside `@wordpress/scripts` block fixes that upstream has already shipped
elsewhere. **`@wordpress/scripts@34.1.0` is already the latest release**, so
there is no version to upgrade to, and `npm audit fix` moves nothing on its own
(it reports 32 → 32). npm's own suggestion to "fix" this by installing
`@wordpress/scripts@19.2.4` is a *downgrade* and is wrong.

## Who maintains these

**Renovate**, not Dependabot. Renovate's npm manager treats `overrides` as a
first-class dependency type, so it opens PRs bumping these pins as upstream
releases land. Dependabot cannot: it bumps declared ranges and lockfile entries
only, with no mechanism to author or update an override — which is exactly why
this repo accumulated 22 open Dependabot PRs, one per transitive package, none of
which any declared dependency could reach. `.github/dependabot.yml` disables
Dependabot version updates for that reason and groups any future security
updates into a single PR.

**Validation:** `.github/workflows/override-check.yml` runs
`bin/check-overrides.mjs` weekly and on PRs touching `package.json`. It
re-resolves the tree *without* overrides and audits it — if an override's
package no longer audits as vulnerable, upstream shipped the fix and the job
fails to tell us to delete the override. Renovate keeps `@wordpress/scripts`
current, so upstream fixes arrive on their own; the check turns them into a
removal signal.

## Active overrides

Six overrides clear all 32 advisories. Each fixes a whole cluster, so the list is
deliberately shorter than the advisory count — pinning the leaves as well (an
earlier draft pinned 20 packages) is redundant once the parent moves.

They are **nested (parent-scoped)** rather than global. `"adm-zip": "^0.6.0"` at
the top level would force that version everywhere in the tree; nesting it under
`@wordpress/scripts` changes only the path that actually needs it, so a future
dependency that legitimately wants a different major is unaffected. It also makes
each entry self-documenting — the parent named in `package.json` *is* the
blocking pin. Verified equivalent: both forms produce zero advisories and a
single copy of each package, with no duplicate left behind at a vulnerable
version.

| Override | Blocking pin | Clears | Remove when |
| --- | --- | --- | --- |
| `lighthouse` → `@sentry/node: ^10.70.0` | `lighthouse` pins `@sentry/node@^9.28.1` | 16 entries: `@sentry/node` plus the whole `@opentelemetry/*` set (`core`, `resources`, `sdk-trace-base`, `sql-common` and 12 `instrumentation-*`). Sentry 9 pins the `@opentelemetry/core@1.x` line, which carries an unbounded-memory advisory fixed in otel 2.8 / Sentry ≥ 10.54. | `lighthouse` allows `@sentry/node` ≥ 10.54 |
| `lighthouse` → `puppeteer-core: ^25.8.0` | `lighthouse` pins `puppeteer-core@^24.17.1` | `puppeteer-core`, `@puppeteer/browsers`, `extract-zip`, `lighthouse` | wp-scripts ships a puppeteer chain > 24.43.1 |
| `@wordpress/scripts` → `adm-zip: ^0.6.0` | `@wordpress/scripts` pins `adm-zip@^0.5.9` | `adm-zip` (4 GB memory-allocation DoS, fixed in 0.6.0) | wp-scripts allows `adm-zip` ≥ 0.6.0 |
| `@wordpress/scripts` → `markdownlint-cli: ^0.49.1` | `@wordpress/scripts` pins `markdownlint-cli@^0.31.1` (2022-era) | `markdownlint-cli`, `markdownlint`, `markdown-it@12`, `linkify-it@3`, `minimatch` | wp-scripts modernises its markdownlint-cli pin |
| `@wordpress/scripts` → `copy-webpack-plugin: ^14.0.0` | `@wordpress/scripts` pins `copy-webpack-plugin@^10.2.0`, which pins `serialize-javascript@6` | `copy-webpack-plugin`, `serialize-javascript` (RCE via `RegExp.flags` + CPU-exhaustion DoS; all 6.x affected) | wp-scripts bumps copy-webpack-plugin to a major allowing serialize-javascript 7.x |
| `@wordpress/scripts` → `webpack-dev-server: ^6.0.0` | `@wordpress/scripts` pins `webpack-dev-server@^5.2.1` (also reached via `@pmmmwh/react-refresh-webpack-plugin`, which npm dedupes to the overridden copy) | `webpack-dev-server`, `sockjs`, `uuid@8` | wp-scripts moves to webpack-dev-server ≥ 6 |

## Risk notes

The aggressive majors here (`webpack-dev-server` 5 → 6, `copy-webpack-plugin`
10 → 14, `puppeteer-core` 24 → 25, `uuid` 8 → 14 transitively) would be risky in
a project that actually ran the wp-scripts commands they back. This project runs
none of them:

- `webpack-dev-server` only matters to `wp-scripts start`, which has no source
  to serve here.
- `copy-webpack-plugin` and `serialize-javascript` sit inside the webpack build,
  which compiles nothing — `npm run build` exits 0 with "Source directory 'src'
  was not found".
- `adm-zip` backs `wp-scripts plugin-zip`; the release pipeline builds its zip
  with `zip -r` in `.github/workflows/release-please.yml`, not that command.
- `puppeteer-core`, `lighthouse` and the Sentry/otel chain back wp-scripts' e2e
  and performance testing. There are no e2e tests in this repo.
- `markdownlint-cli` backs `wp-scripts lint-md-docs`, which is not wired up.

Verified after pinning: `npm run build` exits 0, and commitlint still accepts a
valid message and rejects an invalid one — the two things npm is actually load
bearing for here.

## If the check fails

`bin/check-overrides.mjs` exits 1 with a `✖ STALE` line naming the override.
Delete that entry from `package.json`, regenerate the lockfile
(`npm install --package-lock-only`), confirm `npm audit` still reports zero, and
update the table above.

One limitation to be aware of: the script strips **all** overrides at once
before auditing, so it cannot tell that one override is redundant because a
*different* override already fixes it transitively. That is how the earlier
20-entry list passed the check while six entries did the same job. When adding
an override, check whether bumping the parent already covers it.
