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
- 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 ${MAX_SY
- Refusing to extract entry outside of destination: ${entry.fi
- Corrupted download Expected downloaded file to have checksu
- Corrupted download Expected downloaded file to have checksu
- Corrupted download Expected downloaded file to have size: $
AI-assisted analysis of cypress-io/cypress@0d85fdc912 (2026-08-12).
Data as JSON: /api/errors/50a4da2d7b074176.
Report an issue: GitHub.