{"record":{"id":"b4ade01b2d119588","repo":"vercel-labs/skills","slug":"archive-contains-unsafe-path-path","errorCode":null,"errorMessage":"Archive contains unsafe path: ${path}","messagePattern":"Archive contains unsafe path: (.+?)","errorType":"validation","errorClass":"ArchiveValidationError","httpStatus":null,"severity":"critical","filePath":"src/download-source.ts","lineNumber":147,"sourceCode":"  } catch {\n    return false;\n  }\n}\n\nasync function extractZip(\n  filePath: string,\n  extractDir: string,\n  limits: DownloadLimits\n): Promise<void> {\n  const files = readZipArchive(await readFile(filePath), {\n    maxExtractedBytes: limits.extractMaxBytes,\n    maxEntries: limits.extractMaxFiles,\n  });\n\n  for (const [path, contents] of files) {\n    const targetPath = join(extractDir, path);\n    if (!isPathSafe(extractDir, targetPath)) {\n      throw new ArchiveValidationError(`Archive contains unsafe path: ${path}`);\n    }\n\n    await mkdir(dirname(targetPath), { recursive: true });\n    await writeFile(targetPath, contents);\n  }\n}\n\nfunction getTarEntryType(entry: tar.ReadEntry | Stats): string {\n  if (entry instanceof tar.ReadEntry) {\n    return entry.type;\n  }\n  if (entry.isFile()) return 'File';\n  if (entry.isDirectory()) return 'Directory';\n  return '';\n}\n\nfunction isTarEntryFile(entry: tar.ReadEntry | Stats): boolean {\n  const type = getTarEntryType(entry);","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/download-source.ts#L129-L165","documentation":"Before writing each extracted zip entry, extractZip joins the entry path with extractDir and validates it with isPathSafe; if the resolved target escapes the extraction directory, it throws ArchiveValidationError. This is the defense against Zip Slip (CVE-2018-12689-style path traversal), where an entry named ../../.bashrc would otherwise overwrite files outside the extraction root.","triggerScenarios":"Extracting an archive containing entries with absolute paths (/etc/...), drive letters (C:\\...), ../ sequences, or symlink-style traversal that resolves outside extractDir. Usually seen with maliciously crafted archives or buggy packaging scripts that preserve absolute paths.","commonSituations":"Downloading skill archives from untrusted third parties, archives created with absolute paths by misconfigured build tools, or penetration-test payloads deliberately containing traversal entries.","solutions":["Do not extract the archive — treat it as hostile and remove it","Inspect the offending entries: unzip -l archive.zip | grep -E '(^|/)(\\.\\.|/)\\.' or list entries for ../ patterns","Obtain the archive from a trusted source or repackage its safe contents yourself","Report the malicious archive to wherever it was hosted"],"exampleFix":"# before\nskills add https://untrusted.example/skills.zip  # unsafe path: ../../.ssh/authorized_keys\n\n# after\n# inspect first\nunzip -l untrusted-skills.zip\n# then install from the canonical, trusted repo instead\nskills add verified-org/agent-skills","handlingStrategy":"validation","validationCode":"// Reject archives containing traversal-style entries before extracting\nimport { createReadStream } from 'node:fs';\nimport * as unzipper from 'unzipper';\nasync function assertNoUnsafeEntries(path: string): Promise<void> {\n  const dir = await unzipper.Open.file(path);\n  for (const f of dir.files) {\n    const norm = f.path.replace(/\\\\/g, '/');\n    if (norm.startsWith('/') || /^[A-Za-z]:/.test(norm) || norm.split('/').includes('..')) {\n      throw new Error(`Unsafe entry: ${f.path}`);\n    }\n  }\n}","typeGuard":"function isSafeEntryPath(entryPath: string): boolean {\n  const norm = entryPath.replace(/\\\\/g, '/');\n  if (norm.startsWith('/') || /^[A-Za-z]:/.test(norm)) return false;\n  return !norm.split('/').includes('..');\n}","tryCatchPattern":"try {\n  await extractArchive(file);\n} catch (err) {\n  if (err instanceof ArchiveValidationError && err.message.startsWith('Archive contains unsafe path')) {\n    // SECURITY event: quarantine the archive, do NOT extract with other tools blindly\n  } else throw err;\n}","preventionTips":["Only install archives from trusted, pinned sources","Always extract into a disposable temp directory you control","Audit third-party archives (unzip -l) for absolute paths and ../ segments before use"],"tags":["security","zip-slip","path-traversal","archive","validation"],"backgroundTag":"path-traversal-attack","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}