{"record":{"id":"e4bfbc665fe8e652","repo":"can1357/oh-my-pi","slug":"invalid-cpio-archive-member-name-is-not-nul-termi","errorCode":null,"errorMessage":"Invalid CPIO archive: member name is not NUL-terminated","messagePattern":"Invalid CPIO archive: member name is not NUL-terminated","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cpio.ts","lineNumber":239,"sourceCode":"\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\n\t\tmetadataSize += dataOffset - offset;\n\t\tassertIndexSize(metadataSize, options.limits, \"CPIO index\");\n\t\tconst rawName = decodeUtf8(bytes.subarray(nameStart, nameEnd - 1));\n\t\tif (rawName === TRAILER_NAME) {\n\t\t\tif (header.fileSize !== 0) throw new ArchiveError(\"Invalid CPIO archive: TRAILER!!! has non-empty data\");\n\t\t\tfoundTrailer = true;\n\t\t\toffset = nextOffset;\n\t\t\tbreak;\n\t\t}\n\n\t\tconst fileType = header.mode & FILE_TYPE_MASK;\n\t\tif ((fileType === FILE_TYPE_DIRECTORY || fileType === 0o010000) && header.fileSize !== 0) {","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cpio.ts#L221-L257","documentation":"After bounds-checking the name field, readCpioEntriesFromBuffer asserts the last byte of the declared name field is NUL (bytes[nameEnd-1] === 0) and throws ArchiveError if not. CPIO names are NUL-terminated strings whose size includes the terminator; a missing NUL means the writer under-counted nameSize, wrote the name without a terminator, or the archive bytes are shifted/corrupt.","triggerScenarios":"Parsing an archive where the name field's final byte is non-NUL — a writer that set nameSize = name.length (excluding NUL) while still writing the name, a writer that omitted the NUL entirely, or offset drift so the checked byte lands on padding or data.","commonSituations":"Hand-rolled or minimal archive writers forgetting the terminator; archives post-processed/patched incorrectly; corruption in the name region; mixing formats where alignment assumptions moved the name boundary.","solutions":["Regenerate the archive with standard cpio tooling (GNU cpio, bsdtar) which always NUL-terminates names","Fix your writer: write name + '\\0' and set nameSize = nameBytes.length + 1 so the terminator lies inside the declared field","Compare a known-good archive's hex dump against yours at the failing entry to spot the missing NUL or offset drift","Validate generated archives in CI by round-tripping through this reader before shipping"],"exampleFix":"// before: terminator written outside declared field\nbuf.write(name, nameOffset); buf.writeUInt8(0, nameOffset + name.length); header.nameSize = name.length;\n// after: terminator inside the field\nheader.nameSize = Buffer.byteLength(name) + 1;\nbuf.write(name + '\\0', nameOffset);","handlingStrategy":"try-catch","validationCode":"// validate your writer output before shipping\nconst out = buildCpio(entries);\nreadCpioEntriesFromBuffer(out, options); // throws early on missing NUL terminators","typeGuard":null,"tryCatchPattern":"try {\n  const entries = await readCpio(source, options);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('not NUL-terminated')) {\n    // regenerate/repack the archive; check the writer's nameSize accounting\n  } else throw err;\n}","preventionTips":["Write member names with a trailing NUL and set nameSize = byteLength(name) + 1","Round-trip custom archives through this reader before distribution","Avoid post-processing archive bytes in place","Use GNU cpio/bsdtar as a reference implementation for header layout"],"tags":["archive","cpio","nul-terminator","parsing"],"backgroundTag":"corrupt-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}