{"record":{"id":"f46783ad56a2f847","repo":"can1357/oh-my-pi","slug":"invalid-cpio-archive-name-size-must-include-a-nul","errorCode":null,"errorMessage":"Invalid CPIO archive: name size must include a NUL terminator","messagePattern":"Invalid CPIO archive: name size must include a NUL terminator","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cpio.ts","lineNumber":227,"sourceCode":"\tif (path.posix.isAbsolute(portableTarget)) return { path: portableTarget, resolveTarget: false };\n\tconst normalized = normalizeArchiveLookupPath(path.posix.join(path.posix.dirname(recordPath), portableTarget));\n\treturn normalized === undefined\n\t\t? { path: portableTarget, resolveTarget: false }\n\t\t: { path: normalized, resolveTarget: true };\n}\n\n/** Parse an already-materialized CPIO stream for direct and RPM-composed readers. */\nexport function readCpioEntriesFromBuffer(bytes: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {\n\tassertInMemorySize(bytes.byteLength, options.limits);\n\tconst records: ParsedRecord[] = [];\n\tlet offset = 0;\n\tlet metadataSize = 0;\n\tlet foundTrailer = false;\n\n\twhile (offset < bytes.byteLength) {\n\t\tconst header = parseHeader(bytes, offset);\n\t\tif (header.mode > 0xffff) throw new ArchiveError(\"Invalid CPIO archive: mode exceeds 16 bits\");\n\t\tif (header.nameSize < 1) throw new ArchiveError(\"Invalid CPIO archive: name size must include a NUL terminator\");\n\t\tassertArchivePathBytes(header.nameSize - 1, \"member path\", options.limits.maxPathBytes);\n\t\tassertArchiveMemberSize(header.fileSize, \"(CPIO entry)\", options.limits);\n\n\t\tconst nameStart = offset + header.headerSize;\n\t\tconst nameEnd = nameStart + header.nameSize;\n\t\tconst dataOffset = align(nameEnd, header.alignment);\n\t\tconst dataEnd = dataOffset + header.fileSize;\n\t\tconst nextOffset = align(dataEnd, header.alignment);\n\t\trequireRange(bytes, nameStart, nameEnd, \"member name\");\n\t\trequireRange(bytes, dataOffset, dataEnd, \"member data\");\n\t\trequireRange(bytes, dataEnd, nextOffset, \"member padding\");\n\t\tif (bytes[nameEnd - 1] !== 0) throw new ArchiveError(\"Invalid CPIO archive: member name is not NUL-terminated\");\n\t\tfor (let index = nameStart; index < nameEnd - 1; index++) {\n\t\t\tif (bytes[index] === 0) throw new ArchiveError(\"Invalid CPIO archive: member name contains an embedded NUL\");\n\t\t}\n\t\tvalidateZeroPadding(bytes, nameEnd, dataOffset, \"name\");\n\t\tvalidateZeroPadding(bytes, dataEnd, nextOffset, \"data\");\n","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cpio.ts#L209-L245","documentation":"Every CPIO member name field must contain at least the terminating NUL byte, so header nameSize must be >= 1; the library throws ArchiveError otherwise. A zero nameSize is structurally impossible in conforming archives (even the 'TRAILER!!!' entry has nameSize 11) and would make the name/data offset math nonsensical, so it is rejected as corruption or a crafted header.","triggerScenarios":"Parsing an archive whose header declares nameSize = 0 — from a broken writer, corruption zeroing the field, or a crafted archive (newc hex field '00000000', odc octal '000000').","commonSituations":"Malicious/fuzzed archives in ingestion pipelines; truncated or bit-rotted archives; buggy custom archive writers that forget to count the NUL terminator in nameSize.","solutions":["Regenerate the archive with standard cpio tooling; nameSize must include the trailing NUL","Fix a custom writer to write name bytes plus a NUL and set nameSize = nameBytes.length + 1","Verify the archive against a checksum to distinguish corruption from a hostile input and reject untrusted sources","Locate the offending entry by scanning headers for the zero name-size field before ingestion"],"exampleFix":"// before: writer omits NUL from size\nconst name = Buffer.from(entryPath);\nheader.nameSize = name.length;\n// after: include the NUL terminator\nconst name = Buffer.from(entryPath + '\\0');\nheader.nameSize = name.length;","handlingStrategy":"validation","validationCode":"// pre-check first header before full parse\nconst head = Buffer.from(new Uint8Array(await source.slice(0, 110)));\nconst nameSize = parseInt(head.toString('ascii', 6 + 11 * 8, 6 + 12 * 8), 16);\nif (nameSize < 1) throw new Error('header nameSize must be >= 1');","typeGuard":null,"tryCatchPattern":"try {\n  const entries = await readCpio(source, options);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('name size must include')) {\n    // fix writer to count the NUL or reject the corrupt archive\n  } else throw err;\n}","preventionTips":["Custom writers: nameSize = byteLength(name) + 1 (NUL included)","Round-trip written archives through the reader in CI","Reject untrusted archives declaring zero name sizes","Regenerate corrupted archives from source"],"tags":["archive","cpio","name-size","validation"],"backgroundTag":"corrupt-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}