{"record":{"id":"0becfcc74f5bd531","repo":"can1357/oh-my-pi","slug":"invalid-lzh-archive-too-many-extended-headers","errorCode":null,"errorMessage":"Invalid LZH archive: too many extended headers","messagePattern":"Invalid LZH archive: too many extended headers","errorType":"validation","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":513,"sourceCode":"\t\t\t\tif ((extended[0] === 0x55 || extended[0] === 0x4b) && extended[1] === 0) {\n\t\t\t\t\tosId = extended[0]!;\n\t\t\t\t\tfields.mtimeMs = u32(extended, 2) * 1000;\n\t\t\t\t\tfields.mode = u16(extended, extended.byteLength - 6);\n\t\t\t\t}\n\t\t\t}\n\t\t\tdataStart = baseEnd;\n\t\t} else {\n\t\t\tosId = bytes[offset + 24 + nameLength]!;\n\t\t\tlet extensionSize = u16(bytes, baseEnd - 2);\n\t\t\tlet cursor = baseEnd;\n\t\t\tlet totalExtensionSize = 0;\n\t\t\tlet extensionCount = 0;\n\t\t\twhile (extensionSize !== 0) {\n\t\t\t\tif (extensionSize < 3) throw new ArchiveError(\"Invalid LZH extended header size\");\n\t\t\t\tassertRange(bytes, cursor, cursor + extensionSize, \"extended header\");\n\t\t\t\ttotalExtensionSize += extensionSize;\n\t\t\t\tassertIndexSize(headerLength + 2 + totalExtensionSize, options.limits, \"header metadata\");\n\t\t\t\tif (++extensionCount > 65_535) throw new ArchiveError(\"Invalid LZH archive: too many extended headers\");\n\t\t\t\tconst type = bytes[cursor]!;\n\t\t\t\tconst dataEnd = cursor + extensionSize - 2;\n\t\t\t\tconst data = bytes.subarray(cursor + 1, dataEnd);\n\t\t\t\tif (type === 0x01 || type === 0x02 || type === 0x44 || type === 0x45) {\n\t\t\t\t\tassertArchivePathBytes(data.byteLength, \"member path\", options.limits.maxPathBytes);\n\t\t\t\t}\n\t\t\t\tprocessExtendedHeader(type, data, cursor + 1, fields);\n\t\t\t\tconst currentSize = extensionSize;\n\t\t\t\textensionSize = u16(bytes, dataEnd);\n\t\t\t\tcursor += currentSize;\n\t\t\t}\n\t\t\tdataStart = baseEnd + totalExtensionSize;\n\t\t\tpackedSize = fields.packedSize ?? packedSize - totalExtensionSize;\n\t\t\tif (packedSize < 0) throw new ArchiveError(\"Invalid LZH level-1 packed size\");\n\t\t}\n\t} else {\n\t\tconst headerLength = u16(bytes, offset);\n\t\tif (headerLength < 26) throw new ArchiveError(\"Invalid LZH level-2 header size\");","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L495-L531","documentation":"A safety-limit error thrown when a level-1 LZH header's extended-header chain exceeds 65,535 records. Legitimate archives never need this many extensions; an unbounded or cyclic chain indicates corruption or a hostile input designed to exhaust resources.","triggerScenarios":"Parsing an archive whose extension chain never terminates properly (a size loop pointing back on itself) or an intentionally crafted zip-bomb-style header.","commonSituations":"Processing untrusted uploads, scanning archives from unknown origins, or files corrupted so the chain cursor cycles.","solutions":["Reject the archive as corrupt or malicious","Rescan with a different extraction tool to confirm the chain is bogus","If it is your own writer's output, fix its extension-size bookkeeping so the chain terminates with a 0x0000 terminator"],"exampleFix":"null","handlingStrategy":"validation","validationCode":"enforceSizeLimit(bytes, 64 * 1024 * 1024);","typeGuard":null,"tryCatchPattern":"try { parse(); } catch (err) { if (err instanceof ArchiveError && err.message.includes('too many')) flagMalicious(err); else throw err; }","preventionTips":["Configure FormatReadOptions.limits for your environment","Treat limit trips as security events and log them"],"tags":["archive","lzh","extended-header","security","dos"],"backgroundTag":"archive-header-bomb","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}