Skip to content

chore(deps): upgrade wxt to 0.21.2 and refresh security overrides - #141

Merged
Seismix merged 4 commits into
devfrom
chore/wxt-0.21
Jul 29, 2026
Merged

chore(deps): upgrade wxt to 0.21.2 and refresh security overrides#141
Seismix merged 4 commits into
devfrom
chore/wxt-0.21

Conversation

@Seismix

@Seismix Seismix commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Upgrades WXT 0.20.27 -> 0.21.2 and re-checks every pnpm security override against
its advisory and its parent's declared range. pnpm audit stays clean.

WXT 0.21 drops its bundled web-ext-run fork and takes web-ext, vite and typescript as
peer dependencies
instead, so vite joins devDependencies explicitly. Most of this
diff follows from that one change. No runtime dependency changed — dompurify is still
the only thing that ships in the bundle, and it is untouched.

Migration

  • wxt/testing barrel is gone — now wxt/testing/fake-browser and
    wxt/testing/vitest-plugin.
  • Stricter generated tsconfig (verbatimModuleSyntax, noUncheckedIndexedAccess,
    noImplicitOverride), kept rather than reverted through prepare:tsconfig. 19 fixes:
    three type-only imports, two override modifiers, and guards where a lookup genuinely
    can be undefined. FALLBACK_ADAPTER needs a non-null assertion because ADAPTERS is
    indexed by a computed expression; it is a non-empty const tuple, so the element
    always exists.
  • Node floor raised to 22 by WXT. package.json gained engines.node, plus
    engineStrict: true — on its own pnpm treats engines as advisory and exits 0. The
    README prerequisite (which said 18+) is corrected; it was wrong in the source package
    AMO reviewers build from
    .

Security overrides

Package Change
yauzl@3 >=3.2.1 — patched release exists now, closing the advisory #137 had to leave open. Scoped to 3.x: only 3.2.0 is affected, and addons-linter separately pulls an unaffected 2.10.0.
shell-quote 1.8.5 -> 1.9.0GHSA-395f-4hp3-45gv is patched in 1.9.0, so the old floor no longer covered the advisory it was added for.
brace-expansion pinned per major (1.1.17 / 2.1.3 / 5.0.8) — see below.
yaml dropped — it only ever arrived via web-ext-run.

The one thing worth reviewing. A single >=5.0.8 floor collapses every consumer onto
v5, which minimatch v9 cannot call (brace_expansion_1.default is not a function) —
that breaks vitest run --coverage. Three majors are in the tree at once (minimatch v3
wants ^1, v9 wants ^2, v10 wants ^5), so each line gets its own floor.

GHSA-mh99-v99m-4gvg then keeps matching, because it declares one collapsed <=5.0.7
range
spanning every major. Both tarballs were unpacked and checked: 1.1.16/2.1.2 have
no EXPANSION_MAX_LENGTH, 1.1.17/2.1.3 do. It is an artefact of the advisory's metadata,
not real exposure, so it is ignored via auditConfig.

minimumReleaseAgeExclude exempts two versions published inside pnpm's default
release-age window; safe to delete once they age out.

Sources zip

WXT 0.21 computes the sources archive as includeSources - excludeSources, globbed off
the filesystem — .gitignore is not consulted. Gitignored build output therefore
ships to AMO whenever it exists at zip time; verified by creating coverage/, dist/
and web-ext-artifacts/ and zipping. All three are now excluded explicitly, along with
CLAUDE.local.md (a machine-local symlink ignored only via ~/.gitignore_global).

New pnpm verify:sources scripts the AMO reproducibility check that was being done by
hand: unpack the archive, install --frozen-lockfile && zip:firefox inside it, then
compare the SHA-256 of every file against the shipped extension. Currently 14/14 match.
Zip containers still differ by a few dozen bytes — DOS timestamps in the entry headers,
which two back-to-back builds differ by as well.

Also fixed

  • pnpm lint was broken outright. It ran pnpm build, which emits chrome-mv3, then
    linted .output/firefox-mv2 — so on a clean checkout it failed with a missing-directory
    error. It builds firefox now.
  • README audited against the actual scripts: the Android example passed --s, which
    the flag parser does not accept; the web-ext.config.ts example used startUrl where
    WXT's option is startUrls; and the zip/test commands were undocumented.

Testing

  • Chrome/Edge — pnpm build
  • Firefox — pnpm build:firefox, pnpm zip:firefox, pnpm verify:sources (14/14 files match)
  • Android — not applicable, no Android-specific change
  • 87 unit tests plus a --coverage run; pnpm check 0 errors; pnpm lint 0 errors and
    the same 5 pre-existing innerHTML warnings as dev; format:check;
    install --frozen-lockfile

Not included

Majors held back on purpose: the ones #137 deferred (vite 8, @sveltejs/vite-plugin-svelte
7, vitest 4) plus jsdom, @types/node, prettier-plugin-svelte, typescript 7 and
web-ext 10.

Follows #137, which left the yauzl advisory open pending an upstream release.

WXT 0.21 drops its bundled web-ext-run fork and takes web-ext, vite and
typescript as peer dependencies instead, so vite joins devDependencies
explicitly. web-ext and typescript were already direct devDependencies.

Migration work the 0.21 breaking changes required:

- The `wxt/testing` barrel export is gone. The fake browser and the Vitest
  plugin now come from `wxt/testing/fake-browser` and
  `wxt/testing/vitest-plugin`.

- The generated .wxt/tsconfig.json enables verbatimModuleSyntax,
  noUncheckedIndexedAccess and noImplicitOverride. Kept those rather than
  reverting them through the prepare:tsconfig hook, which took 19 fixes:
  three type-only imports, two `override` modifiers, and guards where an
  array or record lookup genuinely can be undefined. FALLBACK_ADAPTER
  needs a non-null assertion because ADAPTERS is indexed by a computed
  expression; it is a non-empty const tuple, so the element always exists.

- Sources zips are now `includeSources - excludeSources` and honour
  .gitignore, so stale dist/ and web-ext-artifacts/ output stops shipping
  to AMO. .gitignore did not cover CLAUDE.local.md (ignored globally, and
  symlinked into every worktree) or coverage/, which is now gitignored.

Security overrides, re-checked against each alert and each parent's
declared range:

- yauzl has a patched release now, closing the advisory #137 had to leave
  open. Scoped to the 3.x line: only 3.2.0 is affected, and addons-linter
  separately pulls an unaffected 2.10.0.

- shell-quote floor raised 1.8.5 -> 1.9.0. GHSA-395f-4hp3-45gv is patched
  in 1.9.0, so the old floor no longer covered the advisory it was for.

- brace-expansion pinned per major (1.1.17 / 2.1.3 / 5.0.8). A single
  >=5.0.8 floor collapses every consumer onto v5, which minimatch v9
  cannot call ("brace_expansion_1.default is not a function"), breaking
  `vitest run --coverage`. GHSA-mh99-v99m-4gvg is ignored via auditConfig
  because its single collapsed `<=5.0.7` range keeps matching the 1.x and
  2.x backports; both tarballs were unpacked and confirmed to carry the
  EXPANSION_MAX_LENGTH fix.

- yaml override dropped. It only ever arrived via web-ext-run, and
  nothing pulls it in now except vite's optional peer, which is not
  installed.

svelte, svelte-check and @playwright/test picked up in-range releases.
The majors left outstanding are the ones #137 deliberately held back
(vite 8, @sveltejs/vite-plugin-svelte 7, vitest 4) plus jsdom,
@types/node, prettier-plugin-svelte, typescript 7 and web-ext 10.

Verified: 87 unit tests, a coverage run, svelte-check, prettier, web-ext
lint (0 errors, same 5 warnings as dev), chrome and firefox builds, both
zips, and `pnpm install --frozen-lockfile`.

0.21 raises the Node floor to 22, so the README prerequisite (which said
18+) is now wrong in the source package AMO reviewers build from, and
package.json gained a matching `engines.node` so a wrong runtime fails
fast instead of midway through the build.

Reproducibility checked the way AMO checks it: extracting the sources zip
into a clean directory and running install + zip:firefox there produces
generated files that diff clean against the shipped build, all 14 file
hashes equal. The zip containers differ by 36 bytes, which are the DOS
timestamp fields in each entry header; the same is true of two
back-to-back builds on 0.20, so it is not a regression here.

The rest of the README was audited against the actual scripts while it was
open: the Android example passed `--s`, which the flag parser does not
accept (it takes `-s` or `--source-dir`); zip/test commands were not
documented at all; and the web-ext.config.ts example used `startUrl`,
which WXT does not read — the option is `startUrls`. `pnpm lint` was
broken outright — it ran `pnpm build`, which emits chrome-mv3, and then
linted .output/firefox-mv2, so on a clean checkout it failed with a
missing-directory error. It builds firefox now.
Seismix added 3 commits July 29, 2026 15:59
The previous commit claimed WXT 0.21 honours .gitignore when building the
sources zip. It does not. zip.mjs globs the filesystem with tinyglobby and
never consults git; what actually filters is a hardcoded default list in
resolve-config.mjs (node_modules, web-ext.config.ts, __tests__, *.test.*
and .output) plus dot: false, which is why web-ext.config.ts and the
dotfiles stay out and made the .gitignore theory look right.

So being gitignored is not enough. Verified by creating coverage/, dist/
and web-ext-artifacts/ and running `pnpm zip:firefox`: all three landed in
royalrefresh-1.8.0-sources.zip. `vitest --coverage` and `web-ext build`
each leave one of these behind, so shipping them to AMO only takes running
the wrong command before zipping. This is not hypothetical — the sources
zip built before this fix carried modules/wsl-browser.ts, an untracked
machine-local file.

Adds the three globs back and rewrites the comment, which also wrongly
said dist/ and web-ext-artifacts/ used to be listed here. They never were,
so as written it invited the next maintainer to delete the entries again.
engines.node was added so a wrong runtime fails fast rather than midway
through a build, but pnpm treats it as advisory by default: with a >=99
floor, pnpm 11.7.0 prints "[WARN] Unsupported engine" and still exits 0.
engineStrict turns that into a hard error at install time. Node 24 on CI
and the >=22 floor are unaffected, and the lockfile does not change.

Also notes that the GHSA-mh99-v99m-4gvg ignore is unconditional while the
overrides backing it only cover majors 1, 2 and 5. That is every
brace-expansion in the tree today, but a fourth major arriving via a new
dependency would get no floor and no audit signal either.
AMO reviewers rebuild the add-on from the sources zip, so the archive has
to install and build on its own and produce the extension we shipped.
That check was being done by hand. This scripts it: unpack the archive
into a temp directory, run `pnpm install --frozen-lockfile && pnpm
zip:firefox` there, then compare the SHA-256 of every file in the
resulting extension against the shipped one. Currently 14/14 match.

Missing and extra files are reported alongside hash mismatches, and it
exits non-zero on any of them. The work directory is kept whenever
anything fails, so a broken install or build leaves its logs behind;
--keep holds on to it after a passing run too. Needs unzip on PATH.

Zip containers can differ by a few dozen bytes with every file identical,
because each entry header carries a DOS timestamp — two back-to-back
builds of the same source differ the same way. Only file hashes are
compared; the container sizes are printed for context.
@Seismix
Seismix merged commit 9f39ddc into dev Jul 29, 2026
4 checks passed
@Seismix
Seismix deleted the chore/wxt-0.21 branch July 29, 2026 14:09
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.

1 participant