vercel-labs/skills · critical · ArchiveValidationError
Archive contains unsafe path: ${entryPath}
Error message
Archive contains unsafe path: ${entryPath} What it means
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.
Source
Thrown at src/download-source.ts:185
}
async function extractTar(
filePath: string,
extractDir: string,
limits: DownloadLimits
): Promise<void> {
const state: ExtractState = { bytes: 0, entries: 0 };
let validationError: ArchiveValidationError | undefined;
await tar.x({
strict: true,
filter(entryPath, entry) {
if (validationError) return false;
try {
const safePath = validateArchivePath(entryPath);
if (safePath === null) {
throw new ArchiveValidationError(`Archive contains unsafe path: ${entryPath}`);
}
const targetPath = join(extractDir, safePath);
if (!isPathSafe(extractDir, targetPath)) {
throw new ArchiveValidationError(`Archive contains unsafe path: ${entryPath}`);
}
incrementEntry(state, entry.size, limits);
if (isTarEntryFile(entry)) {
return true;
}
return getTarEntryType(entry) === 'Directory';
} catch (error) {
if (error instanceof ArchiveValidationError) {
validationError = error;
return false;View on GitHub (pinned to 435076e789)
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)
Example fix
# before: archive contains entry like # ../../etc/cron.d/evil cd my-skill && tar -czf ../safe.tar.gz . # after: all entries are relative and contained (./SKILL.md, ./refs/...)
Defensive patterns
Strategy: validation
Validate before calling
import { validateArchivePath } from './download-source';
// or locally:
function isSafeEntry(p: string): boolean {
if (!p || p.includes('\0')) return false;
const norm = p.replace(/\\/g, '/');
return !norm.split('/').some((s) => s === '..') && !norm.startsWith('/');
}
for (const entry of await listArchiveEntries(file)) {
if (!isSafeEntry(entry)) throw new Error(`refusing archive: unsafe entry ${entry}`);
} Type guard
function isArchiveValidationError(e: unknown): e is Error & { name: 'ArchiveValidationError' } {
return e instanceof Error && e.name === 'ArchiveValidationError';
} Try / catch
try {
await downloadSource(url, limits);
} catch (e) {
if (isArchiveValidationError(e)) {
// hostile/corrupt archive — do NOT retry; drop the URL
console.error(`Refusing unsafe archive from ${url}`);
process.exitCode = 1;
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Archive contains unsafe path: ${path}
- Unsafe archive path: ${path}
- Downloaded URL is not a valid SKILL.md file or supported arc
- Invalid skill name: potential path traversal detected
- Unsupported archive format
AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28).
Data as JSON: /api/errors/fab7f226af82a791.
Report an issue: GitHub.