cypress-io/cypress · error · Error

Refusing to extract symlink with target larger than ${MAX_SY

Error message

Refusing to extract symlink with target larger than ${MAX_SYMLINK_TARGET_BYTES} bytes: ${entry.fileName}

What it means

Raised by handleEntry() when a zip entry is a Unix symlink (externalFileAttributes S_IFLNK) whose uncompressedSize exceeds MAX_SYMLINK_TARGET_BYTES (4096, the Linux/macOS PATH_MAX). Targets this large are not legal filesystem paths and would also mean reading an arbitrarily large entry fully into memory, so the extractor refuses up front as a hardening measure.

Source

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

  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) {
    if (entry.uncompressedSize > MAX_SYMLINK_TARGET_BYTES) {
      throw new Error(`Refusing to extract symlink with target larger than ${MAX_SYMLINK_TARGET_BYTES} bytes: ${entry.fileName}`)
    }

    const linkTarget = await readEntryAsString(zipFile, entry, MAX_SYMLINK_TARGET_BYTES)
    const resolvedTarget = path.resolve(path.dirname(fileDest), linkTarget)

    if (
      resolvedTarget !== resolvedDest &&
      !resolvedTarget.startsWith(resolvedDest + path.sep)
    ) {
      throw new Error(`Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}`)
    }

    await fsp.rm(fileDest, { recursive: true, force: true })
    await fsp.symlink(linkTarget, fileDest)

    return
  }

View on GitHub (pinned to 0d85fdc912)

Solutions

  1. Do not use the failing artifact. Re-download the official cypress.zip from the default CDN after clearing the cache.
  2. Unset CYPRESS_INSTALL_BINARY / CYPRESS_DOWNLOAD_MIRROR to restore the trusted source.
  3. Inspect the archive (`unzip -l`) and remove or fix the offending symlink entry before re-zipping if you maintain a custom artifact.

Example fix

# before: custom zip with oversized symlink entry
# after:
unset CYPRESS_INSTALL_BINARY
rm -rf ~/.cache/Cypress
cypress install
Defensive patterns

Strategy: try-catch

Validate before calling

// Reject archives containing oversized symlink entries before extracting:
async function assertNoHugeSymlinks(zipPath: string, maxBytes = 4096) {
  await new Promise<void>((resolve, reject) => {
    yauzl.open(zipPath, { lazyEntries: true }, (err, zf) => {
      if (err) return reject(err)
      zf.on('entry', (entry) => {
        const mode = (entry.externalFileAttributes >>> 16) & 0xffff
        const isSymlink = (mode & 0o170000) === 0o120000
        if (isSymlink && entry.uncompressedSize > maxBytes) {
          return reject(new Error(`Oversized symlink: ${entry.fileName}`))
        }
        zf.readEntry()
      })
      zf.on('end', () => resolve())
      zf.readEntry()
    })
  })
}

Try / catch

try {
  await extractWithYauzl(zipPath, destDir, () => {})
} catch (e) {
  if (e instanceof Error && /symlink with target larger/.test(e.message)) {
    // archive is malformed/malicious — discard and re-download from official source
  }
  throw e
}

Prevention

When it happens

Trigger: Extracting a Cypress binary zip (or a custom CYPRESS_INSTALL_BINARY zip) that contains a symlink entry with an absurdly large target. Only reachable with a malformed or malicious archive; the official Cypress zip never contains such entries.

Common situations: A hand-crafted or corrupted zip fed via CYPRESS_INSTALL_BINARY; a tampered mirror artifact; a fuzz/penetration test payload.

Related errors


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