can1357/oh-my-pi · error · ArchiveError

Archive symlink escapes extraction dir: ${link.path} -> ${li

Error message

Archive symlink escapes extraction dir: ${link.path} -> ${link.target}

What it means

extractArchive encountered a symlink entry whose raw target could not be normalized to an archive-root-relative path (normalizeArchiveLookupPath returned undefined), meaning the target escapes or is malformed beyond the library's containment rules. The library refuses to materialize symlinks that could resolve outside the extraction root.

Source

Thrown at packages/utils/src/ar/open.ts:213

	for (const file of files) {
		const extracted = await archive.readFile(file.path);
		const outputPath = path.resolve(extractRoot, file.path);
		await Bun.write(outputPath, extracted.bytes);
		const permissions = (file.mode ?? 0) & 0o777;
		if (permissions) await fs.chmod(outputPath, permissions);
		count++;
	}

	for (const link of links) {
		const outputPath = path.resolve(extractRoot, link.path);
		// Reader link targets are archive-root-relative (raw targets survive
		// only for links that escape the root, which cannot be materialized).
		// Rewrite to a target relative to the link's own directory so the
		// symlink resolves correctly on disk.
		const normalizedTarget = normalizeArchiveLookupPath(link.target);
		if (normalizedTarget === undefined) {
			throw new ArchiveError(`Archive symlink escapes extraction dir: ${link.path} -> ${link.target}`);
		}
		const resolvedTarget = path.resolve(extractRoot, normalizedTarget);
		if (resolvedTarget !== extractRoot && !resolvedTarget.startsWith(extractRoot + path.sep)) {
			throw new ArchiveError(`Archive symlink escapes extraction dir: ${link.path} -> ${link.target}`);
		}
		await fs.mkdir(path.dirname(outputPath), { recursive: true });
		await fs.symlink(path.relative(path.dirname(outputPath), resolvedTarget) || ".", outputPath);
		count++;
	}

	return count;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. List the archive's entries and rewrite offending symlink targets to archive-root-relative ones, then repack.
  2. Skip or drop symlink entries when extracting untrusted archives (many extractors have an option to ignore links).
  3. If the link is legitimately needed outside the root, create it manually after extraction with your own validated target.
  4. Treat the archive as hostile if it comes from an untrusted source and reject it.
Defensive patterns

Strategy: try-catch

Validate before calling

for (const e of entries) {
  if (e.storage?.type === "link" && path.isAbsolute(e.storage.targetPath)) {
    throw new Error(`absolute symlink target in archive: ${e.path} -> ${e.storage.targetPath}`);
  }
}

Try / catch

try {
  await extractArchive(src, destRoot);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("symlink escapes")) {
    // option: re-extract ignoring link entries
  }
  throw err;
}

Prevention

When it happens

Trigger: extractArchive iterating link entries where normalizeArchiveLookupPath(link.target) is undefined — e.g. a target with a scheme/absolute form or '..'-escaping segments the normalizer rejects — at the first check in open.ts before the resolved-target containment test.

Common situations: Archives (tar, cpio, etc.) containing absolute symlink targets like '/usr/lib/foo' or targets with '../' that climb above the archive root; maliciously crafted archives; archives produced on systems with different link conventions (e.g. Windows-style '|'-separated LZH links).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/926970b27c01f33c. Report an issue: GitHub.