can1357/oh-my-pi · error · ArchiveError
Archive entry escapes extraction dir: ${entry.path}
Error message
Archive entry escapes extraction dir: ${entry.path} What it means
During extractArchive, a member's path, resolved against the extraction root, points outside that root (classic zip-slip / path traversal: '../', absolute paths, or drive-escaping names). The library rejects such entries before writing anything, protecting the filesystem from archive-driven overwrites outside the target directory.
Source
Thrown at packages/utils/src/ar/open.ts:180
*/
export async function extractArchive(
input: ArchiveSource,
destDir: string,
options: OpenArchiveOptions = {},
): Promise<number> {
const archive = await openArchive(input, options);
const extractRoot = path.resolve(destDir);
await fs.mkdir(extractRoot, { recursive: true });
let count = 0;
// Directories first so empty ones materialize, then files, then symlinks
// (a symlink's target may be created after it in index order).
const files: { path: string; mode?: number }[] = [];
const links: { path: string; target: string }[] = [];
for (const entry of archive.indexEntries()) {
const outputPath = path.resolve(extractRoot, entry.path);
if (outputPath !== extractRoot && !outputPath.startsWith(extractRoot + path.sep)) {
throw new ArchiveError(`Archive entry escapes extraction dir: ${entry.path}`);
}
if (entry.isDirectory) {
if (entry.storage?.type !== "link") {
await fs.mkdir(outputPath, { recursive: true });
count++;
}
continue;
}
if (entry.storage?.type === "link") {
links.push({ path: entry.path, target: entry.storage.targetPath });
continue;
}
files.push({ path: entry.path, mode: entry.mode });
}
for (const file of files) {
const extracted = await archive.readFile(file.path);
const outputPath = path.resolve(extractRoot, file.path);View on GitHub (pinned to 9690622007)
Solutions
- Inspect the archive's entry listing (openArchive + indexEntries) and remove/sanitize entries containing '..' segments or absolute paths.
- Repackage the archive with relative, root-contained member paths.
- If the archive is trusted and the escape is intentional, extract members manually with your own path handling instead of extractArchive.
- Report the archive as malicious/corrupt when it comes from an untrusted source.
Defensive patterns
Strategy: try-catch
Validate before calling
const reader = await openArchive(src);
for (const e of reader.indexEntries()) {
const resolved = path.resolve(destRoot, e.path);
if (!resolved.startsWith(destRoot + path.sep)) throw new Error(`unsafe entry: ${e.path}`);
} Try / catch
try {
await extractArchive(src, destRoot);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("escapes extraction dir")) {
// reject the archive; clean any partial output and report to the user
await fs.rm(destRoot, { recursive: true, force: true });
}
throw err;
} Prevention
- Always extract into a dedicated empty directory.
- Pre-scan entry paths with openArchive before extracting untrusted archives.
- Never disable or bypass the containment check for third-party archives.
- Treat traversal entries as malicious and quarantine the archive.
When it happens
Trigger: extractArchive processing an entry whose path.resolve(extractRoot, entry.path) is not the root itself and does not start with extractRoot + path.sep — e.g. an entry named '../evil' or '/etc/passwd' inside the archive. Called by downloadTool and install.
Common situations: Extracting untrusted archives (downloads, package installs) crafted with path-traversal entries; archives produced by tools that wrote absolute member paths; processing old or third-party archives with '..' components.
Related errors
- Archive entry escapes extraction directory: ${archivePath}
- Archive symlink escapes extraction dir: ${link.path} -> ${li
- Archive path cannot contain '..'
- Unsafe embedded addon archive entry: ${filename}
- Destination paths cannot contain parent traversal or NUL byt
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/22832e78551edf8d.
Report an issue: GitHub.