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.
Solutions
- Treat the artifact as untrusted: re-download the official cypress.zip from the default CDN after clearing the cache.
- Unset CYPRESS_INSTALL_BINARY and CYPRESS_DOWNLOAD_MIRROR.
- 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
- Source the binary only from the official CDN or a trusted mirror.
- Discard any archive whose symlink entries try to escape the install root.
- Run extraction in a sandboxed/throwaway directory first.
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
- Refusing to extract symlink with target larger than
- Refusing to extract entry outside of destination
- Cannot find version for component
- Corrupted download Expected downloaded file to have…
- Corrupted download Expected downloaded file to have…
AI-assisted analysis of cypress-io/cypress@0d85fdc912 (2026-08-12).
Data as JSON: /api/errors/50a4da2d7b074176.
Report an issue: GitHub.
Appendix: 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)