Skip to content

Security: combine CVE fixes from #484 + #485 with clean lockfile - #488

Merged
fabiankaegy merged 19 commits into
developfrom
fix/security-cves-combined
Aug 18, 2026
Merged

Security: combine CVE fixes from #484 + #485 with clean lockfile#488
fabiankaegy merged 19 commits into
developfrom
fix/security-cves-combined

Conversation

@fabiankaegy

@fabiankaegy fabiankaegy commented May 5, 2026

Copy link
Copy Markdown
Member

Summary

Combines the two in-flight security PRs into a single branch with a regenerated lockfile so npm install and npm ci both work cleanly on a fresh checkout, and bumps the toolkit's minimum Node version to 20.9 (Node 16 and 18 are EOL).

Security changes

  • Bump copy-webpack-plugin ^11 → ^14 and image-minimizer-webpack-plugin ^3 → ^5 to resolve serialize-javascript RCE / CPU-exhaustion (GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v)

  • Downgrade @linaria/* ^5 → ^4.5.4 in projects/10up-theme to drop happy-dom@10 (CVE-2024-53382)

  • Switch from @linaria/webpack-loader to @linaria/webpack5-loader. The meta-package always installs both the webpack 4 and webpack 5 loaders, dragging webpack@4.47.0 (and a long tail of vulnerable transitive deps — serialize-javascript@<7.0.5, braces@2, micromatch@3, terser-webpack-plugin@1) into every install. Importing the webpack5 loader directly drops the webpack 4 chain entirely. Fixes three high-severity transitive CVEs at the resolved-tree level — these flow through to consumers of the published 10up-toolkit (root-level npm overrides do not).

    Migration for consumers using linaria: replace "@linaria/webpack-loader" with "@linaria/webpack5-loader" in your project's package.json and update any loader: '@linaria/webpack-loader' references in custom webpack configs.

Compatibility / install fixes

  • Bump webpackbar ^6 → ^7. webpack 5.96+ added stricter ProgressPlugin schema validation that webpackbar 6 fails by passing non-schema options into its ProgressPlugin parent. webpackbar 7 routes those options to a separate instance and is forward-compatible.
  • Override @types/node to ^20.19.0 (kept). Newer @changesets/cli pulls in @inquirer/external-editor with @types/node>=18 peer requirement, conflicting with @manypkg/find-root@1's legacy @types/node@^12.7.1. npm ci fails without this override. Upstream blocker: only @changesets/cli@3.0.0-next.2 has migrated off @manypkg/find-root@1.
  • Override minimatch to ^9.0.7 (kept). Patches a ReDoS in the version pinned by @typescript-eslint@^6. Resolved permanently by upgrading @typescript-eslint to v8 — deferred (major bump on @10up/eslint-config with consumer impact).
  • Override stylelint-declaration-strict-value to ~1.10.11 (kept). The 1.11.x line bumped its peer to stylelint ≥16, conflicting with @10up/stylelint-config's stylelint 15 peer. Resolved by upgrading the stylelint config to v16 — deferred (major bump with consumer impact).

Node 20.9 minimum (treated as a minor)

  • engines.node raised to >=20.9.0 in the root, 10up-toolkit, @10up/eslint-config, @10up/stylelint-config, and tenup-theme workspaces
  • CI matrix dropped Node 16 and 18 (EOL); now tests Node 20 + 22 on Ubuntu, plus Node 20 on macOS and Windows
  • engines ranges previously drifted across workspaces (root >=14, theme >=12, eslint/stylelint configs >=16) — now consistent

Treated as a minor changeset bump rather than a major because Node 16 and 18 are EOL upstream.

Other fixes uncovered by getting CI green

  • eslint --fix on packages/toolkit/config/webpack-fast-refresh.config.js (newer prettier preferred a different line-break for the require.resolve call)
  • Rewrote the @10up/eslint-config fixture test to list files explicitly via sorted fs.readdirSync instead of glob patterns. ESLint's lintFiles glob expansion is unreliable on Windows under newer eslint/globby — both absolute drive-letter paths and cwd-relative globs fail to match. Explicit, sorted file arrays sidestep the issue and are deterministic across runners.
  • Cross-platform fixes in packages/toolkit/config/webpack/entry.js: switch useBlockAssets and loadBlockSpecificStyles path comparisons to POSIX form via a small toPosix helper that normalizes backslashes and strips Windows drive letters. Drop a redundant path.resolve() on stylesheet paths that was prepending drive letters on Windows. Also updates the develop test expectation ('editor''example/editor') that was locking in degenerate Linux behavior on backslash inputs.

Test plan

  • Clean npm install (no node_modules, no package-lock.json) succeeds on Node 20 and Node 22
  • npm ci succeeds on Node 20 and Node 22
  • npm run lint — 0 errors, 3 unrelated warnings
  • npm run test -w=packages/toolkit — 96/96 pass
  • npm run test -w=projects/library — 2/2 pass
  • npm run build succeeds across all workspaces
  • npm audit — 0 critical, 0 high (24 total: 4 low, 20 moderate, all in dev tooling)
  • npm ls webpack shows only 5.x lines (no webpack@4.47.0 — proves the linaria webpack 4 chain is gone)
  • CI green on Ubuntu (Node 20 + 22), macOS (Node 20), Windows (Node 20)
  • Smoke npm run dev against 10up-theme

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 5, 2026 06:27
@changeset-bot

changeset-bot Bot commented May 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ae6f06

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
10up-toolkit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates two security-fix branches and regenerates dependency resolution so a fresh install can land on patched transitive versions, while keeping the monorepo’s webpack toolchain working by pinning webpack via npm overrides.

Changes:

  • Upgrades copy-webpack-plugin to ^14 and image-minimizer-webpack-plugin to ^5 in the toolkit package.
  • Downgrades @linaria/* to ^4.5.4 in projects/10up-theme to avoid the happy-dom@10 vulnerability chain.
  • Adds a root-level overrides block (including a webpack pin) and a Changesets entry documenting the security release.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

File Description
projects/10up-theme/package.json Downgrades @linaria/* dependencies to avoid vulnerable transitive deps.
packages/toolkit/package.json Updates webpack-related plugins to patched major versions.
package.json Adds npm overrides (incl. webpack pin) to force patched transitive versions.
.changeset/security-vulnerability-fixes.md Adds release notes for the security patch release.
Comments suppressed due to low confidence (1)

package.json:53

  • engines.node in the repo root is >=14, but multiple workspace packages (including 10up-toolkit) require Node >=16. This can lead to confusing installs where the root appears compatible but workspaces fail. Consider raising the root engines.node to match the strictest workspace requirement.
  },
  "description": "Collection of reusable scripts for 10up development.",
  "engines": {
    "node": ">=14",
    "npm": ">=7"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json Outdated
Comment thread projects/10up-theme/package.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/eslint-config/fixtures/test-eslint-config.js
Comment thread .changeset/security-vulnerability-fixes.md Outdated
fabiankaegy added a commit that referenced this pull request May 5, 2026
…arify changeset

Three review items from #488:

1. entry.js path handling on Windows. Develop's PR #456 added tests
   that mock POSIX paths but fail on Windows because path.resolve
   prefixes a drive letter that the mocked glob results don't have.
   Switch the useBlockAssets and loadBlockSpecificStyles paths to
   POSIX-form throughout: introduce a toPosix helper that converts
   backslashes to forward slashes and strips the drive-letter prefix,
   then build filepaths with posix.join / posix.dirname so the
   prefix-stripping logic works on every platform.

   Also update the 'handles Windows-style paths correctly' test
   expectation from 'editor' to 'example/editor'. The old expectation
   was locking in the degenerate Linux behavior of treating a Windows
   backslash path as a single filename — entry.js never produced a
   correct entry name from that input. With the cross-platform fix it
   does.

2. Sort the eslint-config fixture filenames before handing them to
   ESLint. readdirSync ordering is filesystem-dependent and can vary
   across CI runners.

3. Clarify in the changeset that the npm overrides apply only to this
   monorepo's installs (npm only honors top-level overrides). The
   downstream security guarantee for consumers comes from the major
   plugin bumps (copy-webpack-plugin@^14, image-minimizer-webpack-plugin@^5,
   webpackbar@^7), not from the overrides.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@marcovega

marcovega commented May 13, 2026

Copy link
Copy Markdown
Contributor

While smoke-testing this branch I hit two dev-server issues and traced them to the dependency changes here:

  1. webpack-dev-server v5 schema error: options.proxy should be an array. The proxy in packages/toolkit/config/webpack/devServer.js was still using the v4 object form. Converted it to the v5 array shape.

  2. $RefreshReg$ is not defined from @linaria/webpack5-loader: The theme was downgraded from Linaria 5.x to 4.5.4 on this branch. The existing workaround in packages/toolkit/config/webpack/modules.js uses overrideContext, which only exists on Linaria 5.x (wyw-in-js) and is silently ignored on 4.x. Replaced it with a filter that strips react-refresh/babel from the babel config Linaria evaluates works on both 4.x and 5.x.

Snapshots in packages/toolkit/config/__tests__/__snapshots__/ were refreshed to match. All config tests pass.

Engines tested: 20, 22, 24

I can commit the fixes if you want @fabiankaegy

cc @pdavies88

@fabiankaegy

Copy link
Copy Markdown
Member Author

@marcovega i would love that

@ajmaurya99

ajmaurya99 commented May 13, 2026

Copy link
Copy Markdown

Title

npm install fails: stylelint peer conflict between @10up/stylelint-config and stylelint-declaration-strict-value

Description

@10up/stylelint-config declares peerDependencies.stylelint: ^15.0.0, but dependencies.stylelint-declaration-strict-value: ^1.9.2 resolves to a version (e.g. 1.11.1) whose peer is stylelint >=16 <=17. Those ranges do not overlap, so npm exits with ERESOLVE unable to resolve dependency tree.

Not a Node.js version issue

This failure comes from conflicting peerDependencies in the package graph, not from the Node.js runtime. The same ERESOLVE behavior appears with Node 20 and Node 22 (and would persist on other versions until dependencies are aligned).

Environment (example)

  • Node: v20.19.6 or v22.20.0 — error reproduced on both
  • npm: v10.8.2 / v10.9.3 (typical with the above)
  • Command: nvm use 20 && npm install or nvm use 22 && npm install

Fix (maintainers / local patch)

In packages/stylelint-config/package.json, cap stylelint-declaration-strict-value so npm does not resolve to 1.11.x (which requires stylelint 16+ while the config still peers stylelint 15):

"stylelint-declaration-strict-value": ">=1.9.2 <1.11.0",

(around lines 53–54 in that file, inside dependencies).

After changing that value, npm install from the repo root should complete without this peer conflict.

Workaround (consumers, not ideal): npm install --legacy-peer-deps

Longer-term: bump @10up/stylelint-config (and related Stylelint plugins) to stylelint ^16 and allow stylelint-declaration-strict-value ^1.11, or keep the cap above until that migration ships.

@ajmaurya99

Copy link
Copy Markdown

npm ci succeeds on Node 20 and Node 22
npm run lint — 0 errors, 3 unrelated warnings
npm run test -w=packages/toolkit — 96/96 pass
npm run test -w=projects/library— 2/2 pass
npm run build succeeds across all workspaces
npm audit — 0 critical, 0 high (24 total: 4 low, 20 moderate, all in dev tooling)

All these above commands works as expected.

After running the Smoke npm run dev against 10up-theme
I get the same error as @marcovega

10up-toolkit start --hot starts webpack-dev-server v5, which validates devServer options strictly. The toolkit was still passing proxy as an object (the webpack-dev-server v4 style, e.g. { '/dist': { pathRewrite: … } }).

In v5, proxy must be an array, so the dev server throws:

ValidationError: options.proxy should be an array

and the process exits.

wp-env / WordPress are fine — only the webpack dev server config fails.

Fix: use the v5 array proxy shape and set a target (e.g. devURL / http://localhost:8888).

cc - @fabiankaegy

@marcovega

marcovega commented May 13, 2026

Copy link
Copy Markdown
Contributor

Title

npm install fails: stylelint peer conflict between @10up/stylelint-config and stylelint-declaration-strict-value

I didn't encounter this one, I'll try tonight. Also, I'll do another round of tests after the commit with the fix.

Edit: the commit failed tests, i'll check tonight

cc @ajmaurya99 @fabiankaegy

fabiankaegy and others added 16 commits August 18, 2026 07:12
…lean install

Combines the dependency-bump and overrides-based CVE fixes from both
in-flight security PRs into a single branch and regenerates the lockfile
from scratch so npm install works on a fresh checkout.

- Bump copy-webpack-plugin ^11 -> ^14 and image-minimizer-webpack-plugin
  ^3 -> ^5 to resolve serialize-javascript RCE / DoS
  (GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v)
- Downgrade @linaria/* ^5 -> ^4.5.4 in 10up-theme to drop the happy-dom@10
  CVE-2024-53382 dependency
- Add overrides for braces, cross-spawn, flatted, form-data, immutable,
  micromatch, minimatch, node-forge, path-to-regexp, picomatch, rollup,
  serialize-javascript, simple-git, svgo, tar-fs, ws — patches a long
  tail of transitive ReDoS / prototype-pollution / RCE issues without
  needing major-version bumps
- Pin webpack to ~5.95.0 via overrides — webpack 5.96+ added stricter
  ProgressPlugin schema validation that breaks webpackbar@6, which
  extends ProgressPlugin with name/color/reporters options. Without the
  pin a clean npm install picks up 5.106 and the library project's build
  step fails

Result: 0 critical, 0 high vulnerabilities (down from 7 critical, 24
high). Remaining issues are low/moderate in dev tooling
(@wordpress/env, jest-environment-jsdom) with no upstream fixes yet.

Verified:
- npm install from a clean state (no node_modules, no lockfile) succeeds
- npm run test -w=packages/toolkit: 86/86 pass
- npm run test -w=projects/library: 2/2 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A fresh npm install pulls @changesets/cli@2.31.0 (newer than develop's
2.27.9), which now depends on @inquirer/external-editor with a peer
requirement of @types/node>=18. That conflicts with @manypkg/find-root's
declared @types/node@^12.7.1 (also via changesets). npm install is
lenient about the conflict, but npm ci fails with:

  Invalid: lock file's @types/node@12.20.55 does not satisfy
  @types/node@25.6.0
  Missing: @types/node@12.20.55 from lock file

Override @types/node to ^20.19.0 so the resolution is consistent across
both peer ranges. Types packages are additive, so newer-than-^12.7.1 is
compatible for @manypkg/find-root.

Verified:
- npm ci succeeds locally (1938 packages, ~19s)
- npm run test -w=packages/toolkit: 86/86 pass
- npm run test -w=projects/library: 2/2 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin bumps from #484/#485 (copy-webpack-plugin@^14,
image-minimizer-webpack-plugin@^5) require Node >=20.9 — they call
Array.prototype.toSorted which is Node 20+. Bump engines.node to
>=20.9.0 across the root and toolkit package.json, and update the CI
matrix to test on Node 20 + 22 only (was 16/18/20/22).

This is a breaking change — bump the changeset to major.

Other fixes uncovered by getting CI green:
- Override stylelint-declaration-strict-value to ~1.10.11. The newer
  1.11.x bumped its peer requirement to stylelint>=16, conflicting with
  @10up/stylelint-config which still pins stylelint^15. Without the
  override npm install fails with ERESOLVE on Node 20+ (npm 10.8+).
- eslint --fix on packages/toolkit/config/webpack-fast-refresh.config.js
  to satisfy the prettier/prettier rule (the newer prettier resolution
  changed line-break preferences for the require.resolve call).

Verified:
- npm ci on Node 20.19.6 and Node 22.14.0 succeeds
- npm run lint passes (0 errors, 3 unrelated warnings)
- npm run build succeeds on both
- npm run test succeeds on both

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dows

cli.lintFiles() in newer ESLint requires glob patterns with forward
slashes. path.resolve returns backslash-separated paths on Windows, so
Windows CI failed with "No files matching ...\fail\*.js were found"
once we moved Windows from Node 18 to Node 20. Normalize the fail/pass
glob paths before handing them to ESLint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ESLint's lintFiles rejects absolute Windows paths (D:/...) with the
'no files matched' error even after forward-slash normalization. Pass
the fixture directory as cwd and use cwd-relative globs instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ESLint's lintFiles glob expansion is unreliable on Windows under newer
eslint/globby — absolute drive-letter paths fail, cwd-relative globs
also fail to match. Read the fixture directories with fs.readdirSync
and hand ESLint an explicit array of file paths, which lintFiles
treats as files rather than glob patterns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Node 16 and 18 are EOL, so dropping them is treated as a minor (not
major) per the project's release policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses two PR review items:

1. The root overrides.webpack pin only constrained the monorepo's
   install — published 10up-toolkit consumers would still resolve
   webpack to >=5.96 and hit the webpackbar 6 ProgressPlugin schema
   failure. Bump webpackbar to ^7 instead, which routes its non-schema
   options to a separate WebpackBar instance and only forwards
   schema-valid options to the ProgressPlugin parent. Drop the webpack
   override.

2. Workspace engines.node ranges drifted from the toolkit's
   requirement: tenup-theme declared >=12, eslint-config and
   stylelint-config declared >=16. Bump them all to >=20.9.0 to match
   10up-toolkit and the root.

Snapshot updates: WebpackBarPlugin → WebpackBarProgressPlugin
(webpackbar 7 renamed the exported class).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audited which overrides from #485 were still load-bearing after the
copy-webpack-plugin@^14, image-minimizer-webpack-plugin@^5, and
webpackbar@^7 bumps replaced their old transitive trees.

Kept (verified via removing them and re-auditing):
- @types/node ^20.19.0 — without it, npm ci fails with @InQuirer
  /external-editor peer-dep mismatch
- stylelint-declaration-strict-value ~1.10.11 — 1.11.x bumps stylelint
  peer to >=16, breaks @10up/stylelint-config (stylelint 15 peer)
- braces ^3.0.3, micromatch ^4.0.8, minimatch ^9.0.7,
  serialize-javascript ^7.0.5 — npm audit still surfaces these as
  high-severity if removed

Dropped (verified npm audit still reports 0 critical, 0 high without
them):
- cross-spawn, flatted, form-data, immutable, node-forge,
  path-to-regexp, picomatch, rollup, simple-git, svgo, tar-fs, ws

Net result: same 0 critical / 0 high audit, with 12 fewer overrides
to maintain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merge with develop pulled in PR #456 (leading-slashes asset fix) and its
new test 'handles block assets with leading slashes in entry names',
which fails on Windows. The test mocks process.cwd() to a POSIX path
(/mock/project/root) but path.resolve on Windows prefixes the result
with a drive letter (D:\mock\project\root\includes\blocks) — meanwhile
the mocked glob/join paths have no drive letter. The string replace
that strips the blocks-source prefix never matches, so the entry name
comes out as the full path instead of the block-relative segment.

Normalize both sides to forward-slash POSIX form and strip any drive
letter before doing the prefix replace. This handles real Windows
paths (drive letters consistent on both sides), the mocked Windows
test scenario, and POSIX unchanged.

Develop's own latest CI run is failing on Windows for this same test
— this fix unblocks both branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arify changeset

Three review items from #488:

1. entry.js path handling on Windows. Develop's PR #456 added tests
   that mock POSIX paths but fail on Windows because path.resolve
   prefixes a drive letter that the mocked glob results don't have.
   Switch the useBlockAssets and loadBlockSpecificStyles paths to
   POSIX-form throughout: introduce a toPosix helper that converts
   backslashes to forward slashes and strips the drive-letter prefix,
   then build filepaths with posix.join / posix.dirname so the
   prefix-stripping logic works on every platform.

   Also update the 'handles Windows-style paths correctly' test
   expectation from 'editor' to 'example/editor'. The old expectation
   was locking in the degenerate Linux behavior of treating a Windows
   backslash path as a single filename — entry.js never produced a
   correct entry name from that input. With the cross-platform fix it
   does.

2. Sort the eslint-config fixture filenames before handing them to
   ESLint. readdirSync ordering is filesystem-dependent and can vary
   across CI runners.

3. Clarify in the changeset that the npm overrides apply only to this
   monorepo's installs (npm only honors top-level overrides). The
   downstream security guarantee for consumers comes from the major
   plugin bumps (copy-webpack-plugin@^14, image-minimizer-webpack-plugin@^5,
   webpackbar@^7), not from the overrides.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…irectly

@linaria/webpack-loader is a thin meta-package that always installs both
@linaria/webpack4-loader and @linaria/webpack5-loader, dragging
webpack@4.47.0 (and a long tail of vulnerable transitive deps —
serialize-javascript@<7.0.5, braces@2, micromatch@3,
terser-webpack-plugin@1, etc.) into every install even though only
webpack 5 is used.

Switch to @linaria/webpack5-loader directly. The webpack 4 chain is
gone — npm ls webpack now shows only 5.x lines. This fixes the three
high-severity CVEs (serialize-javascript, braces, micromatch) at the
resolved dep-tree level, which means consumers installing the
published 10up-toolkit get the fix too — root-level npm overrides
don't propagate to dependents.

Changes:
- projects/10up-theme/package.json: @linaria/webpack-loader → @linaria/webpack5-loader
- packages/toolkit/package.json: same swap in optional peerDependencies
- packages/toolkit/config/webpack/modules.js: isPackageInstalled and
  loader name updated
- package.json (root): drop braces, micromatch, serialize-javascript
  overrides. Three remain (minimatch, stylelint-declaration-strict-value,
  @types/node) — each documented in the changeset as temporary
  workaround with its upstream blocker
- snapshot updates: WebpackBar… plus loader name renames

Verified: npm install / npm ci both clean on Node 20 + 22; npm audit
0 critical, 0 high (24 total: 4 low, 20 moderate, all in dev
tooling); npm run test passes 96/96 in toolkit, 2/2 in library; lint
clean; no @linaria/webpack4-loader present in the lockfile.

Migration note added to changeset for downstream consumers using
linaria — same one-line package.json swap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
glob() is already called with absolute: true so filePath is absolute
out of the box. The extra path.resolve(filePath) call was a no-op on
Linux/macOS but on Windows it prepended a drive letter and switched to
backslash separators, producing a path string that didn't match what
the rest of webpack received elsewhere — and broke the new mocked-path
tests added by develop's PR #456.

Drop the redundant resolve() and pass filePath through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restacking onto develop brought in Node 24 support (#491) and the
cross-platform entry fixtures (#492), both of which overlap this branch.

- Keep develop's CI matrix (Node 20/22/24, default 24) rather than this
  branch's 20/22 — the security bumps only require >=20.9, not a ceiling.
- Take >=20.9.0 as the engines floor everywhere, including
  @10up/babel-preset-default which develop had left at >=20. The 20.9
  minimum is required by copy-webpack-plugin@14 and
  image-minimizer-webpack-plugin@5.
- Keep develop's entry test helpers and the entry-win32 suite rather than
  re-adding the hardcoded-C: test this branch restored; the toPosix()
  production fix from this branch supersedes the need for it.
- Style entrypoints are no longer passed through path.resolve, so their
  expected values are now the forward-slash paths fast-glob returns. Drop
  the now-unused native() helper and update the win32 expectations.
- Point the docs and check-engines default at 20.9 to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New advisories landed against dependencies this branch had already bumped,
so re-audit and fix the ones reachable from the published package.

- postcss ^8.4.31 -> ^8.5.26 (sourceMappingURL path traversal)
- sharp 0.32.6 -> ^0.35.3 (inherited libvips CVEs). sharp 0.35 requires
  node >=20.9.0, matching this branch's floor, and ships prebuilt binaries
  as optional deps rather than an install script.
- svgo ^3.2.0 -> ^4.0.2 (removeScripts left some executable scripts intact)
- @wordpress/env ^10.10.0 -> ^11.13.0 in 10up-theme, dev only
  (extract-zip symlink path traversal)

Two upstream behaviour changes needed handling in the image minimizer:

- sharp reports .avif input as format `heif`, and since 0.35 `heif()`
  requires an explicit compression. Passing `compression: 'av1'` produces
  byte-identical output to the previous avif() path (verified at 38899
  bytes on the theme's witcher.avif).
- svgo 4 removed removeViewBox from preset-default, so the old
  `overrides: { removeViewBox: false }` is no longer valid. viewBox is
  preserved by default in svgo 4, so plain preset-default keeps the
  previous behaviour.

The postcss snapshot update is cosmetic: newer postcss preserves blank
lines between rules.

Remaining advisories are documented in the changeset. The two criticals
both come through webpack-dev-server, where the entire 5.x line is
affected and only v6 fixes it — a major upgrade that needs its own PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fabiankaegy
fabiankaegy force-pushed the fix/security-cves-combined branch from 5015b3d to 255421b Compare August 18, 2026 05:52
@fabiankaegy

Copy link
Copy Markdown
Member Author

Restacked onto develop (now includes Node 24 support)

Rebased from the old develop (8686de2) onto current develop (77fb0b4), which since this PR was opened has gained Node 24 support (#491) and the cross-platform block entry fixtures (#492). Both overlapped this branch, so the merge conflicts needed real decisions rather than a mechanical resolution:

Area Conflict Resolution
CI matrix This branch narrowed to Node [20, 22]; develop had [20, 22, 24] Kept develop's, plus Node 24 default and actions/*@v4. The security bumps require a Node floor of 20.9, not a ceiling — nothing here is incompatible with 24.
engines.node >=20 (develop) vs >=20.9.0 (here) Kept >=20.9.0 — it's dependency-driven (copy-webpack-plugin@14, image-minimizer-webpack-plugin@5), so it's the more precise of the two. Also applied to @10up/babel-preset-default, which develop had left at >=20, plus the docs and the check-engines default.
entry.js Competing Windows fixes Kept this branch's toPosix() production fix — normalising in the code is more robust than making fixtures match platform quirks — and kept develop's test helpers plus the entry-win32.js suite, which exercises Windows semantics on every platform. Did not re-add the hardcoded-C: test, which toPosix() makes redundant.

One behavioural consequence needed following through: with resolve() removed from the block-style entrypoints (082aa93), those values are now the forward-slash paths fast-glob returns rather than native-separator paths. The native() test helper was therefore wrong on Windows and has been dropped, with the entry-win32.js expectations updated to match. Verified by running the suite under simulated path.win32 with a drive-qualified root — 11/11 pass, where the pre-fix file failed 9.

All 14 original commits are preserved with authorship (including @marcovega's), so the history is unchanged apart from the two new commits at the tip.

Re-audited for new advisories

Advisories have accumulated since this branch was written, so I re-ran npm audit and fixed what's reachable from the published package:

Bump Severity Fixes
postcss ^8.4.31^8.5.26 high sourceMappingURL path traversal
sharp 0.32.6^0.35.3 high inherited libvips CVEs
svgo ^3.2.0^4.0.2 high removeScripts leaving executable scripts intact
@wordpress/env ^10.10.0^11.13.0 (dev only) high extract-zip symlink traversal

Two upstream behaviour changes had to be handled, both caught by the theme build rather than by tests:

  1. sharp reports .avif input as format heif, and since 0.35 heif() requires an explicit compression — without it the build fails with "Expected one of: av1, hevc for compression". Now passes compression: 'av1', which produces byte-identical output to the old avif() path (38899 bytes on witcher.avif).
  2. svgo 4 removed removeViewBox from preset-default, making the toolkit's overrides: { removeViewBox: false } invalid. viewBox is preserved by default in svgo 4, so plain preset-default keeps the previous behaviour. This is a consumer-facing migration for anyone with a custom svgo.config.js — noted in the changeset.

Nice side effect: sharp@0.35 declares engines.node >=20.9.0, independently confirming this branch's floor, and it ships prebuilt binaries as optional dependency packages instead of an install script, so installs no longer need node-gyp/prebuild-install.

npm audit goes from 44 → 41 total, high 12 → 9.

What I deliberately did not fix

  • webpack-dev-server — both remaining criticals (shell-quote, websocket-driver) plus several highs (ws, http-proxy-middleware, sockjs, launch-editor) all trace to it, and the entire 5.x line is affected — only webpack-dev-server@6 fixes them. That's a major upgrade touching dev-server and HMR config, and this branch already carries dev-server changes, so it wants its own PR. Local dev server only; not built output.
  • @wordpress/envadm-zip/tmp — 11.13.0 is the latest release and still pulls adm-zip@0.5.x. Upstream blocker, dev-only.
  • postcss@8.5.14 still hoisted in this monorepo via stylelint@15/cssnano. Consumers are unaffected (the toolkit declares ^8.5.26), and clearing it locally depends on the already-deferred stylelint@16 upgrade.

I tried adding root overrides for postcss and adm-zip and removed them again — they had no effect, even after regenerating the lockfile from scratch, so shipping them would have been a no-op that reads like a fix.

Two things worth a maintainer's eye

  1. npm ls reports minimatch@9.0.9 invalid against eslint@8's declared ^3.1.2 (and the same from eslint-plugin-import, -jsx-a11y, -react, @eslint/eslintrc). The minimatch: ^9.0.7 override is being applied and is producing a tree npm considers invalid. Lint passes, so nothing is visibly broken, but it's worth confirming that's intended.
  2. The theme build emits a removeViewBox is not part of preset-default warning. I chased this initially thinking svgo 4 caused it — it does not: I reproduced it with svgo 3 pinned back, so it predates this PR. It comes from @svgr/plugin-svgo's own default config in a worker thread.

Verification

On Node 24.19 / npm 11.17, from a clean npm ci against the regenerated lockfile:

  • npm ci — clean, exit 0
  • npm run build — 5/5 workspace compilations, 0 ERROR in
  • npm run test — 27 suites / 109 tests, all passing
  • npm run lint — exit 0
  • Theme build re-run from a cleared dist and webpack cache to confirm the sharp/svgo fixes weren't cached artefacts
  • Entry suite additionally run under simulated path.win32 (drive-qualified root): 11/11

🤖 Generated with Claude Code

fabiankaegy and others added 3 commits August 18, 2026 08:24
CI caught two problems my local macOS run missed.

sharp 0.33+ ships prebuilt binaries as per-platform optional dependencies
(@img/sharp-linux-x64 and friends), and npm only records the host
platform's entries in the lockfile — --os/--cpu are ignored even on a
from-scratch resolution. A lockfile generated on macOS therefore only
contains the darwin-arm64 binaries, so npm ci on ubuntu and windows fails
with "Could not load the sharp module using the linux-x64 runtime".

Revert sharp to the previous 0.32.6 pin, which resolves its binary via an
install script and needs no platform entries in the lockfile. The libvips
advisory is documented in the changeset as a follow-up, along with the
heif compression change 0.35 requires; a NOTE is left in optimization.js
so the next person bumping sharp does not rediscover it.

Also fix a prettier error in the entry fixtures. It only reproduced with
--no-cache: a stale .eslintcache masked it locally.

postcss, svgo and @wordpress/env bumps are unaffected — all pure JS, no
platform-specific binaries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The linaria loader filtered the react-refresh babel plugin out by testing
`id.includes('react-refresh/babel')`, but the id comes from
`require.resolve()`, which returns platform-native separators. On Windows
the id is `...\react-refresh\babel.js`, so the substring never matched and
the plugin stayed in the config Linaria evaluates, tripping the
webpack-fast-refresh snapshot on the windows-latest job.

Resolve the plugin path once at module scope and compare by identity, which
is separator-agnostic. The snapshot serializer normalizes backslashes, which
is why this only ever surfaced as a Windows-only snapshot diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fabiankaegy
fabiankaegy merged commit 8cf3450 into develop Aug 18, 2026
8 checks passed
@fabiankaegy
fabiankaegy deleted the fix/security-cves-combined branch August 18, 2026 07:15
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.

4 participants