{"record":{"id":"fab7f226af82a791","repo":"vercel-labs/skills","slug":"archive-contains-unsafe-path-entrypath","errorCode":null,"errorMessage":"Archive contains unsafe path: ${entryPath}","messagePattern":"Archive contains unsafe path: (.+?)","errorType":"exception","errorClass":"ArchiveValidationError","httpStatus":null,"severity":"critical","filePath":"src/download-source.ts","lineNumber":185,"sourceCode":"}\n\nasync function extractTar(\n  filePath: string,\n  extractDir: string,\n  limits: DownloadLimits\n): Promise<void> {\n  const state: ExtractState = { bytes: 0, entries: 0 };\n  let validationError: ArchiveValidationError | undefined;\n\n  await tar.x({\n    strict: true,\n    filter(entryPath, entry) {\n      if (validationError) return false;\n\n      try {\n        const safePath = validateArchivePath(entryPath);\n        if (safePath === null) {\n          throw new ArchiveValidationError(`Archive contains unsafe path: ${entryPath}`);\n        }\n\n        const targetPath = join(extractDir, safePath);\n        if (!isPathSafe(extractDir, targetPath)) {\n          throw new ArchiveValidationError(`Archive contains unsafe path: ${entryPath}`);\n        }\n\n        incrementEntry(state, entry.size, limits);\n\n        if (isTarEntryFile(entry)) {\n          return true;\n        }\n\n        return getTarEntryType(entry) === 'Directory';\n      } catch (error) {\n        if (error instanceof ArchiveValidationError) {\n          validationError = error;\n          return false;","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/download-source.ts#L167-L203","documentation":"Thrown while extracting a downloaded archive when an entry's path fails archive path validation (validateArchivePath returns null). This is a zip-slip / path-traversal defense: the library refuses to extract entries whose normalized path is unsafe before writing anything to disk.","triggerScenarios":"downloadSource() fetches a URL, the payload is detected as an archive, and tar.extract's filter callback encounters an entry whose path (e.g. '../evil.js', absolute paths, or NUL-prefixed names) does not validate via validateArchivePath. Also thrown if validationError was set by a previous entry.","commonSituations":"Downloading a maliciously or oddly crafted tarball/zip from an untrusted URL; archives created by tools that emit absolute entry names ('/usr/bin/x') or '..' segments; a truncated/corrupted download that garbles entry names.","solutions":["Inspect the downloaded archive locally (tar -tzf file.tar.gz) and identify the offending entry path","If you control the archive, repackage it with relative, contained paths (cd dir && tar -czf ...)","If the URL is third-party, verify the URL points to the intended artifact and is not redirected to an error page or hostile payload","Do not bypass the check: this guard protects against arbitrary file overwrite (zip-slip)"],"exampleFix":"# before: archive contains entry like\n#   ../../etc/cron.d/evil\ncd my-skill && tar -czf ../safe.tar.gz .\n# after: all entries are relative and contained (./SKILL.md, ./refs/...)","handlingStrategy":"validation","validationCode":"import { validateArchivePath } from './download-source';\n// or locally:\nfunction isSafeEntry(p: string): boolean {\n  if (!p || p.includes('\\0')) return false;\n  const norm = p.replace(/\\\\/g, '/');\n  return !norm.split('/').some((s) => s === '..') && !norm.startsWith('/');\n}\nfor (const entry of await listArchiveEntries(file)) {\n  if (!isSafeEntry(entry)) throw new Error(`refusing archive: unsafe entry ${entry}`);\n}","typeGuard":"function isArchiveValidationError(e: unknown): e is Error & { name: 'ArchiveValidationError' } {\n  return e instanceof Error && e.name === 'ArchiveValidationError';\n}","tryCatchPattern":"try {\n  await downloadSource(url, limits);\n} catch (e) {\n  if (isArchiveValidationError(e)) {\n    // hostile/corrupt archive — do NOT retry; drop the URL\n    console.error(`Refusing unsafe archive from ${url}`);\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Only pass trusted, HTTPS URLs to downloadSource","Pre-scan archives with a listing step and reject entries containing '..' or absolute paths","Treat any unsafe-path error as a security event: log the source URL, don't retry"],"tags":["security","path-traversal","zip-slip","archive","download"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}