can1357/oh-my-pi · critical · Error

Unsafe embedded addon archive entry: ${filename}

Error message

Unsafe embedded addon archive entry: ${filename}

What it means

For every entry read from the embedded tar stream, the loader re-checks the entry name against isSafeEmbeddedAddonFilename (non-empty, basename-only, no '/' or '\\'). This defends against a hostile or corrupted archive writing outside targetDir even if the manifest side was clean. It throws instead of extracting the offending entry.

Source

Thrown at packages/natives/native/loader-state.js:527

	const archive = zlib.gunzipSync(fs.readFileSync(archivePath));
	const writtenPaths = [];
	let offset = 0;

	while (offset + 512 <= archive.length) {
		if (isZeroTarBlock(archive, offset)) break;
		const header = archive.subarray(offset, offset + 512);
		const filename = getTarEntryName(header);
		const size = readTarOctal(header, 124, 12);
		const typeflag = header[156] === 0 ? "0" : String.fromCharCode(header[156]);
		offset += 512;

		if (offset + size > archive.length) {
			throw new Error(`Truncated embedded addon archive entry: ${filename}`);
		}

		if (!isSafeEmbeddedAddonFilename(filename)) {
			throw new Error(`Unsafe embedded addon archive entry: ${filename}`);
		}
		if (typeflag !== "0") {
			throw new Error(`Unsupported embedded addon archive entry type ${typeflag}: ${filename}`);
		}

		const file = pending.get(filename);
		if (file) {
			if (typeof file.size === "number" && file.size !== size) {
				throw new Error(`Embedded addon size mismatch for ${filename}: expected ${file.size}, got ${size}`);
			}
			const targetPath = path.join(targetDir, filename);
			writeEmbeddedAddonFile(targetPath, archive.subarray(offset, offset + size));
			pending.delete(filename);
			writtenPaths.push(targetPath);
		}

		offset += Math.ceil(size / 512) * 512;
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Repack the embedded archive so every entry is a flat basename at the archive root (tar -czf addon.tar.gz -C staging . with files directly in staging).
  2. Reinstall omp to restore the official embedded archive.
  3. If building archives yourself, strip directory components with the --transform option or flatten the staging directory before packing.

Example fix

// before (repacking)
$ tar -czf addon.tar.gz out/x64/pi_natives.node
// after
$ cd out/x64 && tar -czf ../../addon.tar.gz pi_natives.node
Defensive patterns

Strategy: validation

Validate before calling

import * as path from "node:path";
function assertFlatArchiveEntries(entryNames) {
  for (const name of entryNames) {
    if (!name || path.basename(name) !== name || name.includes("/") || name.includes("\\")) {
      throw new Error(`Unsafe embedded addon archive entry: ${name}`);
    }
  }
}
// list entries with: tar -tzf addon.tar.gz, then assertFlatArchiveEntries(names)

Type guard

function isFlatEntryName(name) {
  return typeof name === "string" && name.length > 0 &&
    path.basename(name) === name && !/[\\/]/.test(name);
}

Try / catch

try {
  extractEmbeddedAddonArchive({ archivePath, files, targetDir });
} catch (err) {
  if (String(err.message).startsWith("Unsafe embedded addon archive entry:")) {
    // treat archive as hostile/corrupt; refuse to use embedded fallback
  } else throw err;
}

Prevention

When it happens

Trigger: extractEmbeddedAddonArchive encounters a tar entry whose header name (including any ustar prefix at offset 345) contains a path separator, is absolute, or is empty — e.g. an entry named '../x.node' or 'usr/lib/x.node' inside the .tar.gz.

Common situations: A tampered or maliciously modified embedded archive; a packaging bug that stored entries under subdirectories; manual repacking of the addon .tar.gz with paths like './dir/file'.

Related errors


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