LTS-4750: drop extract-zip via @puppeteer/browsers 3.x override (GHSA-jmr9-qjv8-65gv) - #22
Merged
Merged
Conversation
…-jmr9-qjv8-65gv) extract-zip is abandoned — the advisory range is `<= 2.0.1` and 2.0.1 is `latest` (published 2020), so `first_patched_version` is null. There is no version to bump to. The only copy in this tree comes from `@wdio/utils` -> `@puppeteer/browsers` `^2.2.0`. `@puppeteer/browsers` 3.x replaced extract-zip with its own extractor, which validates symlink targets before creating them: if (!isInsideDirectory(folderPath, resolvedLinkTarget)) { throw new Error(`Zip symlink "..." would point outside of the target directory.`) } So overriding `@puppeteer/browsers` to `^3.1.0` removes the vulnerable package rather than papering over it. That drops extract-zip, @types/yauzl, fd-slicer, progress and yauzl@2 from the tree and takes `npm audit` from 12 high to 0 high (all 12 were the same extract-zip root, re-reported up the @wdio/* chain). 3.x makes `proxy-agent` a peerDependency (`>=8.0.1`) instead of a hard dep, so npm pulls a refreshed proxy-agents subtree (proxy-agent 8.0.2, pac-* 9, degenerator 7 + quickjs-wasi, http/https-proxy-agent 9 + proxy-agent-negotiate, socks-proxy-agent 10). All are TooTallNate / vercel-labs / sindresorhus packages and carry no advisories. Pinned to 3.1.0 in the lock (published 2026-08-04) rather than 3.2.0 (2026-08-11) so the `.npmrc` `min-release-age=7` cooldown is respected; the `^3.1.0` range lets it float once 3.2.0 clears it. `engines.node` goes `>=18` -> `>=22.12.0`: @puppeteer/browsers 3.x is ESM-only and sets that floor itself, and with `engine-strict=true` the root floor has to agree or customers get the error from a transitive dep instead of from us. Node 18 (EOL 2025-04-30) and Node 20 (EOL 2026-04-30) are both out of support. Verified: - `npm ci` clean: 0 high / 0 critical (was 12 high); 6 moderate remain (qs/uuid via googleapis <- browserstack-node-sdk), unticketed and below this repo's own `audit-level=high` gate. - No `extract-zip` anywhere in the installed tree. - PoC on a zip carrying `pwned -> ../../../../../../../../../../tmp/lts4750-escape`: extract-zip@2.0.1 writes the symlink and it resolves to /tmp/lts4750-escape, outside the extraction dir; @puppeteer/browsers@3.1.0 rejects it with "would point outside of the target directory" and leaves nothing behind. - Full local run through the real code path (`npx wdio run`): Chromedriver v150.0.7871.189 downloaded and unpacked from a .zip by 3.1.0 (the former extract-zip call site), driver started, live WebDriver session against bstackdemo.com, add-to-cart spec passing.
dheeren-gaud
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: https://browserstack.atlassian.net/browse/LTS-4750
Advisory: GHSA-jmr9-qjv8-65gv / CVE-2026-56876 — extract-zip unvalidated symlink path traversal, CVSS 8.1 (High), CWE-22
Dependabot alert: #143
RCA
extract-zipis abandoned. The advisory range is<= 2.0.1and 2.0.1 islatest(published 2020), so the API reportsfirst_patched_version: null— there is no version to bump to and Dependabot could not open a PR.The single copy in this tree is pulled transitively:
npm auditreported 12 high before this change. All 12 are the same extract-zip root re-reported up the@wdio/*"depends on vulnerable versions of" chain —@puppeteer/browsers,@wdio/utils,@wdio/config,webdriver,webdriverio,expect-webdriverio,@wdio/globals,@wdio/runner,@wdio/local-runner,@wdio/mocha-framework,@wdio/cli. GitHub agrees: exactly one open Dependabot alert (#143).npm's own suggested remediation is
npm audit fix --force→ downgrade the whole suite to@wdio/cli@8.14.6/@wdio/mocha-framework@8.14.0. That is a major downgrade of the product this repo demonstrates, and not acceptable.Fix
@puppeteer/browsers3.x removed the extract-zip dependency and ships its own extractor, which validates symlink targets before creating them (src/fileUtil.ts):So this is not a swap of one unvalidated extractor for another — it is the fix for this exact CWE-22, and the vulnerable package leaves the tree entirely.
"overrides": { + "@puppeteer/browsers": "^3.1.0",3.1.0(published 2026-08-04) rather than3.2.0(2026-08-11) so this repo's.npmrcmin-release-age=7supply-chain cooldown is respected. The^3.1.0range lets the lock float to 3.2.x once it clears the cooldown, per the caret convention adopted in #21.Tree delta
Removed:
extract-zip,@types/yauzl,fd-slicer,progress,yauzl@2.10.0,buffer-crc32@0.2.13.3.x also demotes
proxy-agentfrom a hard dependency to a peer (>=8.0.1), so npm resolves a refreshed proxy-agents subtree:proxy-agent6.5.0 → 8.0.2,pac-proxy-agent/pac-resolver/https-proxy-agent/http-proxy-agent→ 9.x,degenerator→ 7.0.1,socks-proxy-agent→ 10.1.0,get-uri→ 8.0.1, plus three newly-introduced packages. Provenance checked, since this is a security PR:quickjs-wasiproxy-agent-negotiateget-east-asian-widthAll MIT, all from the maintainers of the packages that pull them, and none carry an advisory.
engines.node>=18→>=22.12.0@puppeteer/browsers3.x is ESM-only and declares"node": ">=22.12.0"itself. With this repo'sengine-strict=true, a stale root floor of>=18means a customer on Node 20 gets anEBADENGINEhard error from a transitive dependency instead of a clear message from us — so the root floor has to agree. Node 18 (EOL 2025-04-30) and Node 20 (EOL 2026-04-30) are both out of support as of today; Node 22 is Active LTS..npmrccomment updated to record the reason.Verification
1. Audit — 12 high → 0 high (clean
npm ci, 523 top-level entries):The 6 moderate are pre-existing and untouched:
qs/uuidviagoogleapis←browserstack-node-sdk, unticketed and below this repo's own.npmrcaudit-level=highgate — same call as #20 and #21.2.
extract-zipis gone from the installed tree —find node_modules -name extract-zipreturns nothing;npm lsshows@puppeteer/browsers@3.1.0 overridden.3. PoC, before and after, on the same malicious archive. Built a zip carrying a traversal symlink (
pwned -> ../../../../../../../../../../tmp/lts4750-escape, entryexternalFileAttributes0xa1ed0000→S_IFLNK) and extracted it with both:3.1.0 throws
... would point outside of the target directoryand leaves no symlink behind.4. Real code path, end to end.
npx wdio runexercises@wdio/utils→setupChromedriver()→@puppeteer/browsers.install(), and Chromedriver ships as a .zip — precisely the archive type that used to route through extract-zip:Live WebDriver session against bstackdemo.com, add-to-cart spec passing. Also replayed the exact
resolveBuildId→canDownload→install→computeExecutablePathsequence@wdio/utilsuses and asserted the unpacked binary reports the right version and lands insidecacheDir.Reachability
Honest read: the local-browser path is reachable in this repo (
wdio.conf.jshasrunner: 'local'with plainchromecapabilities, so a barenpm testdownloads and unpacks Chromedriver locally), but the archive comes from Google'sstorage-for-testingCDN over TLS, not from anywhere an attacker controls. Exploitation needs a compromised or MITM'd CDN response. So: latent, not exploitable as this repo uses it — patched anyway, per this repo's standing precedent of fixing dev-only transitive CVEs throughoverridesrather than dismissing them (#20, #21, LTS-2594/2753/3158/3159/3411/3907/4206, LTS-4004, LTS-2886, LTS-3295).Out of scope — pre-existing bug worth a follow-up
npm testfails onmaintoday, before this PR, unrelated to it:wdio.conf.js:34still carriesservices: ['chromedriver'], butwdio-chromedriver-serviceis not indevDependenciesand is not in the lockfile onmaineither — WDIO 9 handles driver management inside@wdio/utils, so the entry is stale and should just be dropped. Deliberately not touched here to keep a security PR free of behaviour changes; the verification runs above used a scratch copy of the config with that one line removed. Filing separately.Also noted: the
"puppeteer-core": { "tar-fs": "^3.0.7" }override is now vestigial (puppeteer-coreis an optional peer ofwebdriverioand is not installed). Left alone for the same reason.