can1357/oh-my-pi · critical · Error
Archive entry escapes extraction directory: ${archivePath}
Error message
Archive entry escapes extraction directory: ${archivePath} What it means
extractEmbeddedClientArchive() sanitizes each archive entry path and resolves it under the extraction root; if the resolved destination does not remain inside extractRoot, the entry is a zip-slip/path-traversal attempt and the whole extraction is aborted with this error. It protects the filesystem from a maliciously crafted embedded client archive writing outside its directory.
Source
Thrown at packages/stats/src/server.ts:70
function sanitizeArchivePath(archivePath: string): string | null {
const normalized = archivePath.replaceAll("\\", "/").replace(/^\.\//, "");
if (!normalized || normalized === ".") return null;
if (normalized.includes("..") || path.isAbsolute(normalized)) return null;
return normalized;
}
async function extractEmbeddedClientArchive(archiveBytes: Buffer, outputDir: string): Promise<void> {
const archive = new Bun.Archive(archiveBytes);
const files = await archive.files();
const extractRoot = path.resolve(outputDir);
for (const [archivePath, file] of files) {
const sanitizedPath = sanitizeArchivePath(archivePath);
if (!sanitizedPath) continue;
const destinationPath = path.resolve(extractRoot, sanitizedPath);
if (!destinationPath.startsWith(extractRoot + path.sep)) {
throw new Error(`Archive entry escapes extraction directory: ${archivePath}`);
}
await Bun.write(destinationPath, file);
}
}
async function getEmbeddedClientDir(): Promise<string> {
if (!USE_EMBEDDED_CLIENT) return STATIC_DIR;
if (embeddedClientDirPromise) return embeddedClientDirPromise;
if (!EMBEDDED_CLIENT_ARCHIVE) {
throw new Error(
"Embedded stats client bundle missing. Rebuild the omp binary or npm bundle with embedded stats assets.",
);
}
embeddedClientDirPromise = (async () => {
const bundleHash = Bun.hash(EMBEDDED_CLIENT_ARCHIVE).toString(16);
const outputDir = path.join(EMBEDDED_CLIENT_DIR_ROOT, bundleHash);View on GitHub (pinned to 9690622007)
Solutions
- Rebuild the omp binary/npm bundle from a trusted source so the embedded archive is regenerated intact
- Verify the archive entries: any path containing ../ or absolute prefixes indicates a corrupted/tampered bundle
- Ensure sanitizeArchivePath is applied before resolve and the startsWith(extractRoot + path.sep) check runs on the resolved path
- If a legitimate entry triggers it, fix the entry name at build time (no leading separators, no traversal) rather than weakening the check
Example fix
// before (build step producing a bad entry)
entries.set('/../../etc/passwd-content', file);
// after
entries.set('assets/index.js', file); // relative, normalized path inside the archive root Defensive patterns
Strategy: validation
Validate before calling
for (const [archivePath] of files) {
const dest = path.resolve(extractRoot, sanitizeArchivePath(archivePath));
if (!dest.startsWith(extractRoot + path.sep)) {
throw new Error(`Refusing unsafe archive entry before extraction: ${archivePath}`);
}
} Type guard
function isSafeArchivePath(extractRoot: string, archivePath: string): boolean {
const dest = path.resolve(extractRoot, sanitizeArchivePath(archivePath));
return dest.startsWith(extractRoot + path.sep);
} Try / catch
try {
const dir = await getEmbeddedClientDir();
} catch (err) {
if (err.message.startsWith('Archive entry escapes')) {
console.error('Embedded client archive is corrupted or tampered with — rebuild the omp binary from a trusted source.');
process.exit(1); // do not serve from a partially extracted, untrusted archive
}
throw err;
} Prevention
- Never weaken the sanitizeArchivePath + startsWith(extractRoot + path.sep) checks
- Rebuild binaries from trusted sources; treat this error as evidence of tampering/corruption
- Add build-time tests asserting archive entries contain no traversal or absolute paths
- Keep archive entry names relative and normalized at bundle time
When it happens
Trigger: An embedded client archive contains an entry whose path (after sanitizeArchivePath) still escapes the extract root — absolute paths, ../ traversal sequences, or symlinks resolving outside — when getEmbeddedClientDir first extracts the bundle (binary or npm bundle startup).
Common situations: A tampered or corrupted binary where the embedded archive was replaced; a build regression producing malformed archive entry names; extracting on a platform where path.resolve/separators make an edge-case name escape; testing with a hand-built archive containing traversal entries.
Related errors
- Archive entry escapes extraction dir: ${entry.path}
- Archive path cannot contain '..'
- Unsafe embedded addon archive entry: ${filename}
- Archive symlink escapes extraction dir: ${link.path} -> ${li
- 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/1c5612750bb18148.
Report an issue: GitHub.