{"record":{"id":"3473aa691c908920","repo":"can1357/oh-my-pi","slug":"invalid-arj-basic-header-size","errorCode":null,"errorMessage":"Invalid ARJ basic header size","messagePattern":"Invalid ARJ basic header size","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":69,"sourceCode":"}\n\ninterface ArjBlock {\n\tbodyStart: number;\n\tbodySize: number;\n\tnextOffset: number;\n\tmetadataSize: number;\n\tisEnd: boolean;\n}\n\nfunction parseArjBlock(bytes: Uint8Array, offset: number, options: FormatReadOptions): ArjBlock {\n\tassertRange(bytes, offset, offset + 4, \"header signature\");\n\tif (bytes[offset] !== ARJ_SIGNATURE_0 || bytes[offset + 1] !== ARJ_SIGNATURE_1) {\n\t\tthrow new ArchiveError(\"Invalid ARJ header signature\");\n\t}\n\tconst bodySize = u16(bytes, offset + 2);\n\tif (bodySize === 0)\n\t\treturn { bodyStart: offset + 4, bodySize: 0, nextOffset: offset + 4, metadataSize: 4, isEnd: true };\n\tif (bodySize < 30 || bodySize > ARJ_MAX_BASIC_HEADER) throw new ArchiveError(\"Invalid ARJ basic header size\");\n\tconst bodyStart = offset + 4;\n\tconst bodyEnd = bodyStart + bodySize;\n\tassertRange(bytes, bodyStart, bodyEnd + 4, \"basic header\");\n\tif (crc32(bytes.subarray(bodyStart, bodyEnd)) !== u32(bytes, bodyEnd)) {\n\t\tthrow new ArchiveError(\"Invalid ARJ basic header CRC32\");\n\t}\n\tlet cursor = bodyEnd + 4;\n\tlet extensionCount = 0;\n\tfor (;;) {\n\t\tassertRange(bytes, cursor, cursor + 2, \"extended header size\");\n\t\tconst extensionSize = u16(bytes, cursor);\n\t\tcursor += 2;\n\t\tif (extensionSize === 0) break;\n\t\tif (++extensionCount > 65_535) throw new ArchiveError(\"Invalid ARJ archive: too many extended headers\");\n\t\tassertRange(bytes, cursor, cursor + extensionSize + 4, \"extended header\");\n\t\tif (crc32(bytes.subarray(cursor, cursor + extensionSize)) !== u32(bytes, cursor + extensionSize)) {\n\t\t\tthrow new ArchiveError(\"Invalid ARJ extended header CRC32\");\n\t\t}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L51-L87","documentation":"After the signature, ARJ carries a 16-bit basic-header body size. The spec requires a minimum of 30 bytes (fixed header fields) and this reader caps it at ARJ_MAX_BASIC_HEADER (2600) to bound memory use. A size outside that window cannot be a valid ARJ header, so the parser rejects it before allocating or reading the body.","triggerScenarios":"parseArjBlock() reads a u16 at offset+2 that is < 30 or > 2600. Caused by corrupt size fields, parsing a non-header offset that happens to pass the signature check, or a hostile archive with an oversized size field (resource-exhaustion attempt).","commonSituations":"Bit-rot or truncation corrupting the little-endian size bytes (e.g. size read as 0x0005 after a byte flip), fuzzed/malicious archives testing the size cap, or desynchronized custom offset code landing two bytes off so an unrelated u16 is read as the size.","solutions":["Test the archive with `arj l` or 7-Zip; if those also fail, the file is corrupt — restore from backup or re-download.","If only one member is bad, try extracting the archive with a tolerant external tool and re-pack it before feeding it to this library.","Confirm your code does not pass hand-computed offsets into the reader; always chain from the offsets returned by prior parseArjBlock calls.","If you are generating ARJ files with another tool, check that tool emits header sizes in 30..2600 and re-encode with a spec-compliant archiver."],"exampleFix":"// before: parsing a stored offset without validating provenance\nconst block = parseArjBlock(bytes, myHandComputedOffset, options);\n// after: derive offsets from the parser and pre-check the size field\nconst sig = bytes[myOffset] === 0x60 && bytes[myOffset + 1] === 0xea;\nconst size = bytes[myOffset + 2]! | (bytes[myOffset + 3]! << 8);\nif (!sig || size < 30 || size > 2600) {\n  throw new Error(`No valid ARJ header at offset ${myOffset} (size=${size})`);\n}\nconst block = parseArjBlock(bytes, myOffset, options);","handlingStrategy":"validation","validationCode":"// Confirm the u16 header size at a candidate block offset is within the spec window\nfunction looksLikeArjHeader(bytes: Uint8Array, offset: number): boolean {\n  if (offset + 4 > bytes.byteLength) return false;\n  if (bytes[offset] !== 0x60 || bytes[offset + 1] !== 0xea) return false;\n  const size = bytes[offset + 2]! | (bytes[offset + 3]! << 8);\n  return size === 0 || (size >= 30 && size <= 2600);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return readArj(data, options);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"basic header size\")) {\n    throw new Error(\"ARJ file corrupt or misaligned: header size field out of range\");\n  }\n  throw err;\n}","preventionTips":["Chain block offsets only from parseArjBlock return values — never compute them by hand.","Test downloaded archives (sha256 + `arj t`) before automated parsing.","Keep options.limits enabled so hostile size fields fail fast."],"tags":["archive","corrupt-file","size-limit","parsing"],"backgroundTag":"corrupt-archive-header-size","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}