cypress-io/cypress · error · Error

Refusing to extract entry outside of destination: ${entry.fi

Error message

Refusing to extract entry outside of destination: ${entry.fileName}

What it means

Raised by handleEntry() during zip extraction (extract-with-yauzl) when an archive entry's fileName resolves to a path outside the destination directory — the classic 'Zip Slip' traversal (e.g. an entry named ../../etc/passwd). This is a deliberate security guard: the installer refuses to write any file that would escape the install root, rather than refuse the whole archive.

Source

Thrown at cli/lib/tasks/extract-with-yauzl.ts:95

          zipFile.readEntry()
        })
        .catch(fail)
      })

      zipFile.readEntry()
    })
  })
}

const handleEntry = async (zipFile: any, entry: any, resolvedDest: string): Promise<void> => {
  const fileDest = path.resolve(resolvedDest, entry.fileName)

  // refuse anything that would write outside the install dir
  if (
    fileDest !== resolvedDest &&
    !fileDest.startsWith(resolvedDest + path.sep)
  ) {
    throw new Error(`Refusing to extract entry outside of destination: ${entry.fileName}`)
  }

  const unixMode = (entry.externalFileAttributes >>> 16) & 0xffff
  // Some archivers mark directories by Unix mode bits instead of (or in
  // addition to) a trailing slash; honor both so we don't extract a
  // directory entry as a zero-byte file.
  const isDir = /\/$/.test(entry.fileName) || (unixMode & S_IFMT) === S_IFDIR
  const isSymlink = (unixMode & S_IFMT) === S_IFLNK

  if (isDir) {
    await fsp.mkdir(fileDest, { recursive: true })

    return
  }

  await fsp.mkdir(path.dirname(fileDest), { recursive: true })

  if (isSymlink) {

View on GitHub (pinned to 0d85fdc912)

Solutions

  1. Do not trust the failing artifact: re-download the official cypress.zip from the default source after clearing the cache.
  2. Unset CYPRESS_INSTALL_BINARY and CYPRESS_DOWNLOAD_MIRROR so the installer uses the official CDN.
  3. If you control the zip, regenerate it with relative entry paths that stay under the extraction root (no leading slash, no .. segments).
  4. Scan the archive with `unzip -l` and inspect any entry whose path contains `..` or an absolute prefix.

Example fix

# before: CYPRESS_INSTALL_BINARY=./modified-cypress.zip triggers the guard
# after:
unset CYPRESS_INSTALL_BINARY
rm -rf ~/.cache/Cypress
cypress install
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-scan a zip before extracting to confirm every entry resolves inside dest:
import yauzl from 'yauzl'
import path from 'path'

async function assertSafeZip(zipPath: string, destDir: string) {
  const dest = path.resolve(destDir)
  await new Promise<void>((resolve, reject) => {
    yauzl.open(zipPath, { lazyEntries: true }, (err, zf) => {
      if (err) return reject(err)
      zf.on('entry', (entry) => {
        const resolved = path.resolve(dest, entry.fileName)
        if (resolved !== dest && !resolved.startsWith(dest + path.sep)) {
          return reject(new Error(`Unsafe entry: ${entry.fileName}`))
        }
        zf.readEntry()
      })
      zf.on('end', () => resolve())
      zf.readEntry()
    })
  })
}

Try / catch

try {
  await extractWithYauzl(zipPath, destDir, () => {})
} catch (e) {
  if (e instanceof Error && /Refusing to extract entry outside of destination/.test(e.message)) {
    // artifact is untrusted — do not retry; re-download from the official source
  }
  throw e
}

Prevention

When it happens

Trigger: Extracting a Cypress binary zip (cypress.zip) that contains an entry whose path, joined to resolvedDest and normalized, does not share the resolvedDest prefix. In practice this means the published artifact is malformed/tampered, or a custom CYPRESS_INSTALL_BINARY points at a crafted zip.

Common situations: Pointing CYPRESS_INSTALL_BINARY at an untrusted or hand-modified zip; a mirror serving a tampered artifact; a corrupted download that produced garbage entry names; an attacker-supplied test fixture fed to the extractor.

Related errors


AI-assisted analysis of cypress-io/cypress@0d85fdc912 (2026-08-12). Data as JSON: /api/errors/3bc33624fb69e14b. Report an issue: GitHub.