{"record":{"id":"aaa0b348f6a7c93d","repo":"neoclide/coc.nvim","slug":"refusing-to-extract-through-symbolic-link-curre","errorCode":null,"errorMessage":"Refusing to extract through symbolic link: ${current}","messagePattern":"Refusing to extract through symbolic link: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/model/download.ts","lineNumber":149,"sourceCode":"        if (!isDirectory) {\n          let input = await openZipEntry(zipfile, entry)\n          await writeZipEntry(input, target)\n        }\n        zipfile.readEntry()\n      }, fail).catch(fail)\n    })\n    zipfile.readEntry()\n  })\n}\n\nasync function ensureNoSymlink(dest: string, target: string): Promise<void> {\n  let relative = path.relative(dest, target)\n  let current = dest\n  for (let part of relative.split(path.sep).filter(Boolean)) {\n    current = path.join(current, part)\n    try {\n      let stat = await fs.promises.lstat(current)\n      if (stat.isSymbolicLink()) throw new Error(`Refusing to extract through symbolic link: ${current}`)\n      if (!stat.isDirectory()) throw new Error(`Invalid extraction directory: ${current}`)\n    } catch (e) {\n      if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e\n    }\n  }\n}\n\nasync function writeZipEntry(input: NodeJS.ReadableStream, target: string): Promise<void> {\n  let flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC\n  if (typeof fs.constants.O_NOFOLLOW === 'number') flags |= fs.constants.O_NOFOLLOW\n  let handle = await fs.promises.open(target, flags, 0o666)\n  try {\n    await pipeline(input, handle.createWriteStream())\n  } finally {\n    await handle.close().catch(() => undefined)\n  }\n}\n","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/neoclide/coc.nvim/blob/50e974d9692461a69147d5cab146a8d3e439abe4/src/model/download.ts#L131-L167","documentation":"During archive extraction, ensureNoSymlink in src/model/download.ts walks each path component between the destination root and the target file. If any intermediate component is a symbolic link, extraction is aborted with this error. This is a zip-slip / symlink-traversal security guard preventing archives from writing files outside the destination via planted symlinks.","triggerScenarios":"Extracting an archive (unzipFile) that contains a symlink entry (e.g. 'link' -> '/etc') followed by a file under that symlink ('link/evil.txt'); the lstat of an intermediate component detects a symlink.","commonSituations":"Malicious or tampered archive downloads; archives produced on systems using symlinks (macOS frameworks, node_modules packed with symlinks); downloading untrusted binaries via the download API.","solutions":["Inspect the archive for symlink entries and remove them (e.g. `zip -d file.zip 'link*'` or repack without symlinks).","Only extract archives from trusted sources; verify checksums/signatures before download.","If you legitimately need symlinks, extract to a location where the link targets are inside dest, or extract manually with symlinks resolved to real copies."],"exampleFix":"// before\ndownload({ url, dest: '/opt/app', extract: true }) // archive contains symlink entries\n// after\n// repack archive without symlinks, then\ndownload({ url: sanitizedUrl, dest: '/opt/app', extract: true })","handlingStrategy":"try-catch","validationCode":"import { execSync } from 'child_process'\n// pre-check archive for symlink entries (zip example):\nconst out = execSync(`unzip -Z1 -v ${archive}`).toString()\nif (/symlink/i.test(out)) throw new Error('archive contains symlinks; refusing to extract')","typeGuard":null,"tryCatchPattern":"try {\n  await download({ url, dest, extract: true })\n} catch (e) {\n  if (String(e.message).startsWith('Refusing to extract through symbolic link')) {\n    console.error('Archive contains symlinks; extract to a throwaway dir or reject the source')\n  } else throw e\n}","preventionTips":["Only extract archives from trusted, checksum-verified sources.","Scan archives for symlink entries before extraction.","Extract untrusted archives into an isolated throwaway directory first."],"tags":["security","symlink","archive-extraction","zip-slip"],"backgroundTag":"symlink-traversal-extraction-blocked","analyzedSha":"50e974d9692461a69147d5cab146a8d3e439abe4","analyzedAt":"2026-08-31T11:17:23.966Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}