{"record":{"id":"23f264071ad6cb8c","repo":"can1357/oh-my-pi","slug":"archive-member-formatarchivepathforerror-member-23f264","errorCode":null,"errorMessage":"Archive member '${formatArchivePathForError(memberPath)}' has an invalid size","messagePattern":"Archive member '(.+?)' has an invalid size","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":78,"sourceCode":"\n\tconstructor(buffer: Uint8Array, dataOffset: number, sparse: boolean) {\n\t\tthis.#buffer = buffer;\n\t\tthis.#dataOffset = dataOffset;\n\t\tthis.#sparse = sparse;\n\t}\n\n\tasync read(size: number, memberPath: string): Promise<Uint8Array> {\n\t\tif (this.#sparse) {\n\t\t\tthrow new ArchiveError(\n\t\t\t\t`Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read`,\n\t\t\t);\n\t\t}\n\t\tif (size > this.#buffer.byteLength - this.#dataOffset) {\n\t\t\tthrow new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' is truncated`);\n\t\t}\n\t\tconst bytes = this.#buffer.subarray(this.#dataOffset, this.#dataOffset + size);\n\t\tif (bytes.byteLength !== size) {\n\t\t\tthrow new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' has an invalid size`);\n\t\t}\n\t\treturn bytes;\n\t}\n}\n\nfunction readTarString(buffer: Uint8Array, offset: number, length: number): string {\n\tconst limit = Math.min(offset + length, buffer.byteLength);\n\tlet end = offset;\n\twhile (end < limit && buffer[end] !== 0) end++;\n\treturn TEXT_DECODER.decode(buffer.subarray(offset, end));\n}\n\nfunction bytesEqualAscii(bytes: Uint8Array, value: string): boolean {\n\treturn bytes.byteLength === value.length && bytesMatchAscii(bytes, 0, value);\n}\n\nfunction isUstarHeader(buffer: Uint8Array, offset: number): boolean {\n\treturn (","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L60-L96","documentation":"After slicing the member bytes, TarMemberSource.read() confirms the subarray length equals the requested size. This is a final invariant check — if a subarray within a valid buffer somehow returns fewer bytes (offset arithmetic overflow near Uint8Array length boundaries, or size larger than Number-safe limits interacting with subarray clamping), the read is rejected rather than returning mis-sized data. In practice it is a defensive backstop behind the truncation check at line 73.","triggerScenarios":"A read(size, path) call where dataOffset + size exceeds Uint8Array indexing limits or otherwise causes subarray() to clamp: sizes beyond the buffer's max safe range, or arithmetic producing a end offset past the typed array's length — caught only after the line-73 pre-check passed.","commonSituations":"Archives with extremely large members near typed-array limits (multi-GB in-memory tar buffers); a size field parsed via BigInt path that overflows Number precision; corrupted headers producing sizes that pass the coarse check but overflow the slice arithmetic.","solutions":["Check the member's declared size in the header for corruption (huge or negative values) — re-obtain a known-good archive","Process very large tar archives via fileByteSource/streaming instead of buffering fully in memory","Cap member sizes with archive limits (maxMemberSize) before extraction so oversized declares are rejected up front","Report/fix if a custom code path passes a computed size inconsistent with the header (off-by-one, wrong units)"],"exampleFix":"// before: reading member with unchecked header-declared size\nconst size = readMemberSize(header);\nconst data = await member.read(size, path);\n// after: validate the declared size against limits first\nconst size = readMemberSize(header);\nif (!Number.isSafeInteger(size) || size < 0 || size > limits.maxMemberSize) {\n\tthrow new Error(`implausible member size ${size} for ${path}`);\n}\nconst data = await member.read(size, path);","handlingStrategy":"validation","validationCode":"const declared = readMemberSize(header);\nif (!Number.isSafeInteger(declared) || declared < 0 || declared > limits.maxMemberSize) {\n\tthrow new Error(`corrupt tar header: implausible member size ${declared}`);\n}","typeGuard":"function hasPlausibleSize(size: unknown): size is number {\n\treturn typeof size === \"number\" && Number.isSafeInteger(size) && size >= 0;\n}","tryCatchPattern":"try {\n\tconst data = await entry.source.read(entry.size, entry.path);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"invalid size\")) {\n\t\tthrow new Error(`member ${entry.path} has a corrupt or overflowing size field; archive is untrustworthy`, { cause: err });\n\t}\n\tthrow err;\n}","preventionTips":["Enforce archive limits (maxMemberSize) so oversized header declares are rejected before read()","Sanity-check parsed tar size fields for safe-integer range and sign before use","For very large archives, prefer file-backed sources over full in-memory buffers to avoid typed-array limits","Verify archive checksums — this error usually indicates a corrupted size field rather than a caller bug"],"tags":["tar","bounds-check","corruption","archive"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}