can1357/oh-my-pi · critical · Error

Unsafe embedded addon archive entry

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.

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.

Appendix: 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)