vercel-labs/skills · critical
Unsafe archive path: ${path}
Error message
Unsafe archive path: ${path} What it means
Inside addArchiveFile, every archive entry path must pass normalizeArchivePath; if it returns falsy (null/empty) the path is unsafe (traversal, absolute, or malformed) and the provider refuses to store the file. This is the well-known provider's zip-slip guard.
Source
Thrown at src/providers/wellknown.ts:703
if (rawPath.startsWith('/') || rawPath.startsWith('\\')) return null;
if (/^[A-Za-z]:/.test(rawPath)) return null;
if (rawPath.includes('\\')) return null;
const parts = rawPath.split('/').filter(Boolean);
if (parts.length === 0) return null;
if (parts.some((part) => part === '.' || part === '..')) return null;
return parts.join('/');
}
private addArchiveFile(
files: Map<string, WellKnownFileContent>,
path: string,
content: Uint8Array,
runningTotal: { bytes: number }
) {
const normalizedPath = this.normalizeArchivePath(path);
if (!normalizedPath) throw new Error(`Unsafe archive path: ${path}`);
runningTotal.bytes += content.byteLength;
if (runningTotal.bytes > MAX_ARCHIVE_UNPACKED_BYTES) {
throw new Error('Archive exceeds maximum unpacked size');
}
if (files.size >= MAX_ARCHIVE_FILES) {
throw new Error('Archive contains too many files');
}
files.set(normalizedPath, content);
}
private extractTarGz(bytes: Uint8Array): Map<string, WellKnownFileContent> {
const tar = gunzipSync(Buffer.from(bytes));
const files = new Map<string, WellKnownFileContent>();
const runningTotal = { bytes: 0 };
let offset = 0;
View on GitHub (pinned to 435076e789)
Solutions
- Download the artifact manually and list entries (unzip -l / tar -tzf) to find the offending path
- Rebuild the artifact with relative POSIX entry names from inside the skill directory
- Verify the artifact URL/content checksum; re-publish the registry artifact
- Treat repeated occurrences as a security signal and stop trusting that registry source
Defensive patterns
Strategy: validation
Validate before calling
function normalizeArchivePath(p: string): string | null {
if (!p || p.includes('\0')) return null;
const n = p.replace(/\\/g, '/').replace(/^\/+/, '');
if (n.split('/').some((s) => s === '..' || s === '')) return null;
return n;
}
if (!normalizeArchivePath(entryPath)) skipAndWarn(entryPath); Type guard
function isUnsafeArchivePath(e: unknown): e is Error {
return e instanceof Error && /Unsafe archive path/.test(e.message);
} Try / catch
try { await provider.fetchSkills(); }
catch (e) {
if (isUnsafeArchivePath(e)) { auditLog.warn(`hostile artifact from registry: ${e.message}`); return []; }
throw e;
} Prevention
- Only consume well-known registries you trust
- Pre-validate every entry name before storing extraction results
- Alert on unsafe-path failures — they indicate hostile or broken artifacts
When it happens
Trigger: A fetched registry archive containing entries like '../x', '/etc/passwd', empty names, or names that normalize to nothing — normalizeArchivePath rejects them and this error throws during extraction.
Common situations: Compromised or mis-built registry artifacts; archives produced by tools emitting absolute paths; corrupted downloads that scramble entry names.
Related errors
- Archive contains unsafe path: ${path}
- Archive contains unsafe path: ${entryPath}
- Archive exceeds maximum unpacked size
- Archive contains too many files
- Archive links are not supported
AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28).
Data as JSON: /api/errors/9fe14a40b70fd389.
Report an issue: GitHub.