Skip to content

fix(cli): extract the Cypress binary with yauzl instead of extract-zip - #33887

Merged
mschile merged 16 commits into
developfrom
mschile/mystifying-kowalevski-3cd77f
May 22, 2026
Merged

fix(cli): extract the Cypress binary with yauzl instead of extract-zip#33887
mschile merged 16 commits into
developfrom
mschile/mystifying-kowalevski-3cd77f

Conversation

@mschile

@mschile mschile commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Additional details

The test-binary-against-kitchensink* CircleCI jobs (electron, chrome, firefox) started failing on develop on 2026-05-21 with:

The cypress npm package is installed, but the Cypress binary is missing.
We expected the binary to be installed here: /root/.cache/Cypress/beta-15.16.0-develop-<sha>/Cypress/Cypress

The trigger was a Node patch release picked up by nvm install 24:

Run Node npm Cypress binary after install
Last passing v24.15.0 11.12.1 15.16.0
First failing v24.16.0 11.13.0 not installed

Root cause

Cypress's postinstall runs as expected on Node 24.16.0, but the zip-extraction pipeline inside extract-zip silently truncates after the first compressed entry. Bisected with minimal reproductions, the failure is in this pipeline:

yauzl@2.x  ReadStream   →   zlib.createInflateRaw()   →   fs.createWriteStream

yauzl@2.x's read stream uses fd-slicer, which hasn't been updated in 8 years. Something about how its Readable interacts with a zlib Transform changed in Node 24.16.0: _read is invoked, bytes accumulate in the source's internal buffer, but zero bytes are forwarded into InflateRaw — and no data, end, close, or error event fires on any stream. The process exits cleanly with code 0 because nothing keeps the event loop alive, and the post-install reports success despite having extracted only the first file in the archive.

The CircleCI failures came from the trixie base image not having the system unzip tool installed, so the Linux job fell back to the Node-based extractor (extract-zipyauzl@2.xfd-slicer). macOS (ditto) and most user environments with a working system unzip were unaffected.

yauzl@3.x dropped fd-slicer in favor of reading via fs.read directly and is unaffected by the regression.

The fix

  • Bump yauzl to ^3.3.1 and drop extract-zip.
  • Replace the Node fallback in cli/lib/tasks/unzip.ts with a small extractor (cli/lib/tasks/extract-with-yauzl.ts) built on yauzl + fs.createWriteStream. The new extractor:
    • preserves Unix file modes (so the binary keeps its +x bit),
    • recreates directories and symlinks,
    • refuses entries whose resolved path escapes the destination,
    • refuses symlinks whose target resolves outside the destination,
    • caps the in-memory read of a symlink body to PATH_MAX (4096 bytes).
  • The OS-tool primary paths (unzip on Linux, ditto on macOS) are unchanged.

Follow-up

Windows currently uses the Node-based extractor as its primary path because tar.exe wasn't available when the original code was written. Windows 10 1803+ has shipped tar.exe for years, so a follow-up task can migrate Windows to match the Linux/macOS pattern of "OS tool primary, Node fallback." That is beyond the scope of this regression fix.


Note

High Risk
Changes the Cypress binary extraction path used during cypress install (including Windows’ primary unzip implementation), so any regressions could break installs across platforms; it also touches archive-handling logic where mistakes can have security implications.

Overview
Fixes a Node 24.16+/26.1+ regression where Cypress installation could silently extract only the first file by removing extract-zip and switching the CLI’s Node-based unzip to a new yauzl-driven extractor.

Adds extract-with-yauzl to safely recreate directories, files, and symlinks while preserving Unix modes, and includes guards against path traversal and out-of-destination symlinks plus a capped in-memory symlink-target read. Updates unzip progress handling to only open the zip for entry counting, bumps yauzl to ^3.3.1, updates/extends unit tests (including new extractor tests), and documents the fix in the CLI changelog.

Reviewed by Cursor Bugbot for commit ca5287a. Bugbot is set up for automated code reviews on this repo. Configure here.

Steps to test

  • Confirm the test-binary-against-kitchensink, test-binary-against-kitchensink-chrome, and test-binary-against-kitchensink-firefox jobs pass on this PR.
  • Confirm test-binary-against-recipes*, test-binary-against-cypress-realworld-app, and the macOS/Windows binary jobs still pass.
  • Confirm the new cli unit tests in cli/test/lib/tasks/unzip.spec.ts pass (yarn workspace cypress test-unit -- unzip.spec.ts).

How has the user experience changed?

No user-visible change. The binary that lands on disk is identical; only the extraction path that produced it has changed.

PR Tasks

  • [na] Is there an associated issue with maintainer approval for PR submission?
  • Have tests been added/updated?
  • [na] Has a PR for user-facing changes been opened in cypress-documentation?
  • [na] Have API changes been updated in the type definitions?

npm 11.13.0 (bundled with Node 24.16+) stops running the cypress tarball's
postinstall lifecycle script during `npm install --legacy-peer-deps
~/cypress/cypress.tgz`, which left the binary missing and broke the
kitchensink binary jobs once nvm started resolving Node 24 to 24.16.0.
Invoke `cypress install` explicitly so the binary is in place regardless
of whether npm runs the tarball's postinstall.
@mschile
mschile requested a review from a team as a code owner May 21, 2026 20:44
cypress-bot[bot]
cypress-bot Bot previously approved these changes May 21, 2026
@cypress-bot
cypress-bot Bot dismissed their stale review May 21, 2026 20:55

New commits pushed (auto-approval was for 1be7353, head is now bd7903d). Auto-approval dismissed pending Cursor Bugbot re-review of the new head SHA.

cypress-bot[bot]
cypress-bot Bot previously approved these changes May 21, 2026
@mschile mschile self-assigned this May 21, 2026
@cypress

cypress Bot commented May 21, 2026

Copy link
Copy Markdown

cypress    Run #71013

Run Properties:  status check failed Failed #71013  •  git commit ca5287ab46: docs(cli): include Node 26.1.0+ in the affected versions
Project cypress
Branch Review mschile/mystifying-kowalevski-3cd77f
Run status status check failed Failed #71013
Run duration 19m 35s
Commit git commit ca5287ab46: docs(cli): include Node 26.1.0+ in the affected versions
Committer Matthew Schile
View all properties for this run ↗︎

Test results
Tests that failed  Failures 1
Tests that were flaky  Flaky 11
Tests that did not run due to a developer annotating a test with .skip  Pending 1112
Tests that did not run due to a failure in a mocha hook  Skipped 2
Tests that passed  Passing 27304
View all changes introduced in this branch ↗︎
UI Coverage  63.51%
  Untested elements 27  
  Tested elements 47  
Accessibility  99.01%
  Failed rules  0 critical   3 serious   1 moderate   0 minor
  Failed elements 19  

Tests for review

Failed  cypress/e2e/studio/studio-state-management.cy.ts • 1 failed test • app-e2e

View Output

Test Artifacts
Cypress Studio - State Management > remains in studio mode when the test name is changed on the file system and file watching is disabled Test Replay Screenshots
Flakiness  commands/net_stubbing.cy.ts • 1 flaky test • 5x-driver-chrome:beta

View Output

Test Artifacts
... > stops waiting when an fetch request is canceled Test Replay
Flakiness  commands/waiting.cy.js • 1 flaky test • 5x-driver-chrome:beta

View Output

Test Artifacts
... > errors > throws when route is never resolved Test Replay
Flakiness  e2e/origin/config_env_expose.cy.ts • 1 flaky test • 5x-driver-chrome:beta

View Output

Test Artifacts
cy.origin- Cypress.config() > serializable > overwrites different values in secondary if one exists in the primary Test Replay
Flakiness  commands/files.cy.js • 1 flaky test • 5x-driver-chrome:beta

View Output

Test Artifacts
... > throws when there is an error reading the file Test Replay
Flakiness  issues/28527.cy.ts • 1 flaky test • 5x-driver-chrome:beta

View Output

Test Artifacts
issue 28527 > fails and then retries and verifies about:blank is not displayed Test Replay Screenshots

The first 5 flaky specs are shown, see all 11 specs in Cypress Cloud.

The 'Ensure Cypress binary is installed' step hung in CI: only Listr's
hide-cursor escape made it to the log before the 10m no-output timeout.
Setting npm_config_loglevel=silent triggers cypress install's silent
renderer (silentRendererCondition in cli/lib/tasks/install.ts), so the
unzip runs without a spinner. Also drop the npx layer and invoke the
cypress bin directly.
@cypress-bot
cypress-bot Bot dismissed their stale review May 21, 2026 21:44

New commits pushed (auto-approval was for bd7903d, head is now f8cc525). Auto-approval dismissed pending Cursor Bugbot re-review of the new head SHA.

cypress-bot[bot]
cypress-bot Bot previously approved these changes May 21, 2026
Bump yauzl to v3.x, remove extract-zip, and replace the Node-based unzip
fallback in cli/lib/tasks/unzip.ts with a small extractor built on yauzl
+ fs.createWriteStream. The new extractor preserves Unix file modes,
recreates symlinks, refuses entries (and symlink targets) that would
escape the destination, and caps in-memory reads of symlink bodies.

Drops the temporary CircleCI workaround that manually installed `unzip`
and extracted the zip ahead of the binary install — no longer needed
once the cypress.tgz built from this branch uses the new extractor.
@cypress-bot
cypress-bot Bot dismissed their stale review May 22, 2026 00:20

New commits pushed (auto-approval was for f8cc525, head is now c769b0a). Auto-approval dismissed pending Cursor Bugbot re-review of the new head SHA.

Comment thread cli/lib/tasks/extract-with-yauzl.ts Outdated
@mschile mschile changed the title chore(ci): explicitly install Cypress binary in test-binary-against-repo fix(cli): extract the Cypress binary with yauzl instead of extract-zip May 22, 2026
mschile added 3 commits May 21, 2026 18:33
- Bugfix entry covering the Node 24.16.0+ install regression and the
  switch off `extract-zip` / `fd-slicer`.
- Dependency-update entry for the yauzl bump and extract-zip removal.
@mschile
mschile requested a review from AtofStryker May 22, 2026 00:40
mschile added 2 commits May 21, 2026 18:49
…th paths

The 'can really unzip' unit test mocks fs-extra globally. On macOS the
test passes through to the real `ditto` binary and never hits the Node
fallback, but on Linux CI `ditto` is missing so the fallback runs — and
extractWithYauzl used fs-extra, whose mocked createWriteStream returned
undefined and crashed the pipeline.

Swap fs-extra for Node's built-in fs (fs.promises) so the test exercises
the same code path on both OSes.
Comment thread cli/lib/tasks/extract-with-yauzl.ts

@AtofStryker AtofStryker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@mschile do you think it's worth it here to add tests for extract-with-yauzl? I'd like to see some so in the future we fail "faster" though we do have binary unzipping test coverage in a more e2e fashion throughout the test suite

Comment thread cli/lib/tasks/extract-with-yauzl.ts Outdated
Comment thread cli/lib/tasks/extract-with-yauzl.ts
- Switch the fs.promises import to the more idiomatic fs/promises form
  (PR feedback).
- Remove all zipfile event listeners alongside close() in the finish
  handler so the zipfile object is fully detached when the extract
  settles (PR feedback).
- Add cli/test/lib/tasks/extract-with-yauzl.spec.ts covering:
  - simple deflated file extraction
  - directory entries
  - Unix mode preservation
  - symlinks (+ following them to read target content)
  - path traversal rejection
  - symlink-target-outside-destination rejection
  - declared symlink-size cap
  - streamed-size cap when the declared size lies
  - invalid zip file rejection
Comment thread cli/lib/tasks/extract-with-yauzl.ts Outdated
Comment thread cli/lib/tasks/extract-with-yauzl.ts

@AtofStryker AtofStryker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't have much besides cursor's comments. Great work on getting this prioritized and fixed!

Comment thread cli/CHANGELOG.md Outdated
- Normalize any thrown / rejected value from a per-entry handler into a
  real Error before passing to `finish`, so a falsy rejection
  (e.g. Promise.reject(undefined)) no longer slips through and resolves
  the extraction as a success.
- Recognize directory entries that are marked via the Unix S_IFDIR mode
  bits, not only via a trailing slash on the entry name. Some archivers
  emit one but not the other; the previous code would have extracted
  these as zero-byte regular files.
- Add tests covering both new behaviors.
- Update the changelog entry to reference the related issue.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3a322e0. Configure here.

Comment thread cli/lib/tasks/extract-with-yauzl.ts
@MikeMcC399

This comment was marked as resolved.

Comment thread cli/CHANGELOG.md Outdated
@mschile
mschile merged commit 88dad2e into develop May 22, 2026
92 of 97 checks passed
@mschile
mschile deleted the mschile/mystifying-kowalevski-3cd77f branch May 22, 2026 16:06
clinm pushed a commit to clinm/seed4j that referenced this pull request May 24, 2026
@cypress-bot

cypress-bot Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Released in 15.16.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v15.16.0, please open a new issue.

@cypress-bot cypress-bot Bot locked as resolved and limited conversation to collaborators May 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cypress install silently exits 0 mid-extraction on Node.js 24.16.0

4 participants