can1357/oh-my-pi · error · ArchiveError
Invalid ZIP archive: malformed NTFS extra field
Error message
Invalid ZIP archive: malformed NTFS extra field
What it means
Thrown by parseNtfsMtime while walking the attributes inside a ZIP NTFS extra field (id 0x000a): a sub-attribute record's tag+size runs past the end of the field data. The library treats a malformed NTFS timestamp field as a hard error rather than silently ignoring the optional timestamp.
Source
Thrown at packages/utils/src/ar/zip.ts:235
return info;
}
function filetimeToMs(bytes: Uint8Array, offset: number): number | undefined {
let value = 0n;
for (let index = 7; index >= 0; index--) value = (value << 8n) | BigInt(bytes[offset + index]!);
const milliseconds = value / 10_000n - WINDOWS_EPOCH_FILETIME_MS;
const number = Number(milliseconds);
return Number.isSafeInteger(number) ? number : undefined;
}
function parseNtfsMtime(data: Uint8Array): number | undefined {
if (data.byteLength < 4) return undefined;
let offset = 4;
while (offset + 4 <= data.byteLength) {
const tag = readUInt16LE(data, offset);
const size = readUInt16LE(data, offset + 2);
const end = offset + 4 + size;
if (end > data.byteLength) throw new ArchiveError("Invalid ZIP archive: malformed NTFS extra field");
if (tag === 1 && size >= 8) return filetimeToMs(data, offset + 4);
offset = end;
}
return undefined;
}
function parseDosMtime(time: number, date: number): number | undefined {
if (date === 0) return undefined;
const year = 1980 + ((date >>> 9) & 0x7f);
const month = ((date >>> 5) & 0x0f) - 1;
const day = date & 0x1f;
const hour = (time >>> 11) & 0x1f;
const minute = (time >>> 5) & 0x3f;
const second = (time & 0x1f) * 2;
if (month < 0 || month > 11 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;
const value = new Date(year, month, day, hour, minute, second).getTime();
return Number.isFinite(value) ? value : undefined;
}View on GitHub (pinned to 9690622007)
Solutions
- Test with `unzip -t`; if standard tools accept it, report the incompatibility and re-zip the files.
- Recreate the archive with a standard tool (Info-ZIP, 7-Zip), ideally without NTFS timestamp extras if the reader chokes on them.
- Repair with `zip -FF` if the source archive is corrupt.
- Strip the offending extra field with a zip-manipulation script before reading.
Example fix
// before: reading the problematic archive as-is
const zip = readZip(badArchive);
// after: re-zip cleanly to drop nonstandard extras
await $`unzip -q badArchive.zip -d tmp`;
await $`zip -qr clean.zip .`.cwd("tmp");
const zip = readZip("clean.zip"); Defensive patterns
Strategy: try-catch
Validate before calling
// Not cheaply pre-validable without parsing extras; at minimum confirm the archive passes a reference reader.
const probe = Bun.spawnSync(["unzip", "-t", path]);
if (probe.exitCode !== 0) throw new Error(`archive fails `unzip -t`; repair or re-zip before reading: ${path}`); Try / catch
try {
const zip = readZip(path);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("malformed NTFS extra field")) {
// writer emits broken NTFS timestamps: re-zip without extras
await $`unzip -q ${path} -d tmp`; await $`zip -qr clean.zip .`.cwd("tmp");
return readZip("clean.zip");
}
throw err;
} Prevention
- Run `unzip -t` as an ingestion gate for third-party archives.
- Prefer archives from standard writers; be suspicious of custom backup tools that emit NTFS extras.
- When re-zipping, drop nonessential extended attributes if consumers are strict parsers.
- Report malformed archives to their producer rather than special-casing the parser.
When it happens
Trigger: Calling zip info/read on an archive whose entries carry an NTFS extra field (0x000a) with a reserved 4-byte prefix plus attribute records where any record's declared size overflows the field. Triggered only when this optional extra field is present and internally inconsistent.
Common situations: Archives written by tools that emit NTFS timestamps with incorrect attribute lengths; archives corrupted in the extra-field region; hand-crafted or fuzzed zips.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid ZIP archive: truncated extra-field header
- Invalid ZIP archive: malformed extra field
- Invalid ZIP archive: central directory is out of bounds or m
- Invalid ZIP archive: missing ZIP64 central-directory metadat
- Invalid ZIP archive: truncated central directory
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ed5184b879118565.
Report an issue: GitHub.