cypress-io/cypress · error · Error

Refusing to extract symlink pointing outside of destination:

Error message

Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}

What it means

Raised by handleEntry() after reading a symlink entry whose link target, resolved relative to the entry's directory, would point outside the destination root. This blocks a symlink-based traversal escape (analogous to Zip Slip but via symlinks): even if the entry path itself is inside the dest, the link can still direct writes elsewhere. The error names both the entry and the offending target.

Source

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

    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
  }

  const readStream: NodeJS.ReadableStream = await new Promise((res, rej) => {
    zipFile.openReadStream(entry, (err: any, rs: NodeJS.ReadableStream) => {
      if (err) {
        return rej(err)
      }

      return res(rs)
    })
  })

View on GitHub (pinned to 0d85fdc912)

Solutions

  1. Treat the artifact as untrusted: re-download the official cypress.zip from the default CDN after clearing the cache.
  2. Unset CYPRESS_INSTALL_BINARY and CYPRESS_DOWNLOAD_MIRROR.
  3. If you maintain the zip, ensure every symlink target resolves inside the extraction root (relative, no escaping `..`).

Example fix

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

Strategy: try-catch

Validate before calling

// Reject archives containing symlink entries that resolve outside dest:
async function assertSafeSymlinks(zipPath: string, destDir: string) {
  const dest = path.resolve(destDir)
  // requires reading symlink target bytes; for a quick check, flag entries with '..' targets
  await new Promise<void>((resolve, reject) => {
    yauzl.open(zipPath, { lazyEntries: true }, (err, zf) => {
      if (err) return reject(err)
      zf.on('entry', async (entry) => {
        const mode = (entry.externalFileAttributes >>> 16) & 0xffff
        if ((mode & 0o170000) === 0o120000) {
          const target = await readEntry(zf, entry)
          const resolved = path.resolve(path.dirname(path.join(dest, entry.fileName)), target)
          if (resolved !== dest && !resolved.startsWith(dest + path.sep)) {
            return reject(new Error(`Escaping symlink: ${entry.fileName} -> ${target}`))
          }
        }
        zf.readEntry()
      })
      zf.on('end', () => resolve())
      zf.readEntry()
    })
  })
}

Try / catch

try {
  await extractWithYauzl(zipPath, destDir, () => {})
} catch (e) {
  if (e instanceof Error && /symlink pointing outside of destination/.test(e.message)) {
    // untrusted archive — discard, do not attempt to sanitize in place
  }
  throw e
}

Prevention

When it happens

Trigger: Extracting a Cypress binary zip (or custom CYPRESS_INSTALL_BINARY zip) containing a symlink whose target includes enough `..` segments, or an absolute path, to escape resolvedDest. The target is read via readEntryAsString and resolved with path.resolve before this check.

Common situations: A crafted or corrupted archive supplied via CYPRESS_INSTALL_BINARY; a tampered mirror artifact; security testing of the installer.

Related errors


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