{"record":{"id":"50a4da2d7b074176","repo":"cypress-io/cypress","slug":"refusing-to-extract-symlink-pointing-outside-of-de","errorCode":null,"errorMessage":"Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}","messagePattern":"Refusing to extract symlink pointing outside of destination: (.+?) -> (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/lib/tasks/extract-with-yauzl.ts","lineNumber":125,"sourceCode":"\n    return\n  }\n\n  await fsp.mkdir(path.dirname(fileDest), { recursive: true })\n\n  if (isSymlink) {\n    if (entry.uncompressedSize > MAX_SYMLINK_TARGET_BYTES) {\n      throw new Error(`Refusing to extract symlink with target larger than ${MAX_SYMLINK_TARGET_BYTES} bytes: ${entry.fileName}`)\n    }\n\n    const linkTarget = await readEntryAsString(zipFile, entry, MAX_SYMLINK_TARGET_BYTES)\n    const resolvedTarget = path.resolve(path.dirname(fileDest), linkTarget)\n\n    if (\n      resolvedTarget !== resolvedDest &&\n      !resolvedTarget.startsWith(resolvedDest + path.sep)\n    ) {\n      throw new Error(`Refusing to extract symlink pointing outside of destination: ${entry.fileName} -> ${linkTarget}`)\n    }\n\n    await fsp.rm(fileDest, { recursive: true, force: true })\n    await fsp.symlink(linkTarget, fileDest)\n\n    return\n  }\n\n  const readStream: NodeJS.ReadableStream = await new Promise((res, rej) => {\n    zipFile.openReadStream(entry, (err: any, rs: NodeJS.ReadableStream) => {\n      if (err) {\n        return rej(err)\n      }\n\n      return res(rs)\n    })\n  })\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/cypress-io/cypress/blob/0d85fdc91230885bca0a91df278312800a48b727/cli/lib/tasks/extract-with-yauzl.ts#L107-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"A crafted or corrupted archive supplied via CYPRESS_INSTALL_BINARY; a tampered mirror artifact; security testing of the installer.","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 `..`)."],"exampleFix":"# before: CYPRESS_INSTALL_BINARY=./crafted-cypress.zip\n# after:\nunset CYPRESS_INSTALL_BINARY\nrm -rf ~/.cache/Cypress\ncypress install","handlingStrategy":"try-catch","validationCode":"// Reject archives containing symlink entries that resolve outside dest:\nasync function assertSafeSymlinks(zipPath: string, destDir: string) {\n  const dest = path.resolve(destDir)\n  // requires reading symlink target bytes; for a quick check, flag entries with '..' targets\n  await new Promise<void>((resolve, reject) => {\n    yauzl.open(zipPath, { lazyEntries: true }, (err, zf) => {\n      if (err) return reject(err)\n      zf.on('entry', async (entry) => {\n        const mode = (entry.externalFileAttributes >>> 16) & 0xffff\n        if ((mode & 0o170000) === 0o120000) {\n          const target = await readEntry(zf, entry)\n          const resolved = path.resolve(path.dirname(path.join(dest, entry.fileName)), target)\n          if (resolved !== dest && !resolved.startsWith(dest + path.sep)) {\n            return reject(new Error(`Escaping symlink: ${entry.fileName} -> ${target}`))\n          }\n        }\n        zf.readEntry()\n      })\n      zf.on('end', () => resolve())\n      zf.readEntry()\n    })\n  })\n}","typeGuard":null,"tryCatchPattern":"try {\n  await extractWithYauzl(zipPath, destDir, () => {})\n} catch (e) {\n  if (e instanceof Error && /symlink pointing outside of destination/.test(e.message)) {\n    // untrusted archive — discard, do not attempt to sanitize in place\n  }\n  throw e\n}","preventionTips":["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."],"tags":["install","extract","security","symlink","path-traversal"],"backgroundTag":null,"analyzedSha":"0d85fdc91230885bca0a91df278312800a48b727","analyzedAt":"2026-08-12T16:24:28.056Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}