{"record":{"id":"b471c368f5d54b78","repo":"Hmbown/CodeWhale","slug":"inconsistent-update-sizes-or-compression","errorCode":null,"errorMessage":"Inconsistent update sizes or compression.","messagePattern":"Inconsistent update sizes or compression\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":69,"sourceCode":"  const minimum=Math.max(0,bytes.length-65557); let end=-1;\n  for(let i=bytes.length-22;i>=minimum;i--) if(bytes.readUInt32LE(i)===0x06054b50&&i+22+bytes.readUInt16LE(i+20)===bytes.length) { end=i; break; }\n  if(end<0||bytes.readUInt16LE(end+4)||bytes.readUInt16LE(end+6)) throw new Error(\"Invalid update archive.\");\n  const count=bytes.readUInt16LE(end+10); let position=bytes.readUInt32LE(end+16),total=0;\n  if(!count||count>2000||bytes.readUInt16LE(end+8)!==count||position+bytes.readUInt32LE(end+12)!==end) throw new Error(\"Invalid update archive index.\");\n  const seen=new Set();\n  for(let i=0;i<count;i++) {\n    if(position+46>end||bytes.readUInt32LE(position)!==0x02014b50) throw new Error(\"Invalid update entry.\");\n    const flags=bytes.readUInt16LE(position+8),method=bytes.readUInt16LE(position+10),length=bytes.readUInt16LE(position+28),extra=bytes.readUInt16LE(position+30),comment=bytes.readUInt16LE(position+32);\n    const name=bytes.subarray(position+46,position+46+length).toString(\"utf8\");\n    const kind=(bytes.readUInt32LE(position+38)>>>16)&0xf000,offset=bytes.readUInt32LE(position+42),compressed=bytes.readUInt32LE(position+20);\n    const size=bytes.readUInt32LE(position+24); total+=size;\n    if(flags&1||![0,8].includes(method)||![0,0x4000,0x8000].includes(kind)||total>512*1024*1024||position+46+length+extra+comment>end) throw new Error(\"Unsupported update entry.\");\n    if(!name.startsWith(`${APP_NAME}.app/`)||name.includes(\"\\\\\")||name.includes(\":\")||name.includes(\"\\0\")||name.split(\"/\").some(part=>part===\"..\"||part===\".\")||seen.has(name)) throw new Error(\"Unsafe update path.\");\n    seen.add(name);\n    if(offset+30>position||bytes.readUInt32LE(offset)!==0x04034b50) throw new Error(\"Invalid update file header.\");\n    const localLength=bytes.readUInt16LE(offset+26),localExtra=bytes.readUInt16LE(offset+28);\n    if(offset+30+localLength+localExtra+compressed>bytes.readUInt32LE(end+16)||bytes.subarray(offset+30,offset+30+localLength).toString(\"utf8\")!==name) throw new Error(\"Inconsistent update file header.\");\n    if(bytes.readUInt16LE(offset+8)!==method||bytes.readUInt16LE(offset+6)!==flags||(!(flags&8)&&(bytes.readUInt32LE(offset+18)!==compressed||bytes.readUInt32LE(offset+22)!==size))) throw new Error(\"Inconsistent update sizes or compression.\");\n    const start=offset+30+localLength+localExtra;\n    // Header sizes are untrusted. Bound actual expansion before ditto writes\n    // anything, including a compressed payload whose headers understate size.\n    const payload=bytes.subarray(start,start+compressed);\n    let expanded;\n    try { expanded=method===0?payload.length:inflateRawSync(payload,{maxOutputLength:Math.max(size,1)}).length; }\n    catch { throw new Error(\"Invalid or oversized compressed update entry.\"); }\n    if(expanded!==size) throw new Error(\"The update entry size did not match its contents.\");\n    position+=46+length+extra+comment;\n  }\n  if(position!==end) throw new Error(\"Invalid update archive length.\");\n  return count;\n}\n\nexport async function prepareUpdate(update) {\n  if(!update?.available) throw new Error(\"Check for an available update first.\");\n  if(!newerVersion(update.version,APP_VERSION)||update.url!==`${repository}/releases/download/v${update.version}/Codewhale-Computer-Use-${update.version}-macos-universal.zip`||!Number.isSafeInteger(update.size)||update.size<=0||update.size>limit) throw new Error(\"The update identity is invalid.\");\n  // Only GitHub's fixed release URL and its asset CDN can serve the bytes.","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L51-L87","documentation":"The entry's compression method and sizes recorded in the local header must match the central directory: method and general-purpose flags must be identical, and when bit 3 (data descriptor, sizes in a trailing record) is not set, the local header's compressed and uncompressed sizes must equal the central-directory values. Any disagreement means the archive lies about its layout, so the validator cannot bound what `ditto` will write and rejects the update.","triggerScenarios":"Streaming zips built with `flags&8` where the local header carries placeholder sizes but the flag isn't set as expected, mismatched method/flags between local and central headers (e.g. recompressed entry without updating one directory), or local sizes differing from central sizes.","commonSituations":"Recompressing a single entry with a script that edits the local header but not the central one; zip writers producing streaming (data-descriptor) archives consumed by tools that clear the flag; hand-crafted bombs in security testing; different zip library versions writing inconsistent metadata in a build pipeline.","solutions":["Repackage the whole archive with a single standard tool (`ditto -c -k` or `zip -r`) so both headers are written by the same writer.","Compare `unzip -v` output (method, sizes) against expectations; fix the packaging step that recompresses individual entries in place.","Avoid streaming zip writers for release artifacts; produce a seekable zip with final sizes in both headers.","Verify the published asset's SHA-256 to rule out partial or tampered downloads."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const v = execFileSync(\"unzip\", [\"-v\", zipPath]).toString();\nif (/\\n\\s*\\d+\\s+(Bzip2|LZMA|Zstd)/.test(v)) throw new Error(\"non-deflate method present\");","typeGuard":null,"tryCatchPattern":"try { validateReleaseZip(bytes); } catch (e) { if (e.message === \"Inconsistent update sizes or compression.\") throw new Error(\"Header size/method mismatch — rebuild the archive with one writer\"); throw e; }","preventionTips":["Use a seekable (non-streaming) zip writer for releases","Never recompress individual entries in place after the fact","Check `unzip -v` methods/sizes against expectations in CI","Rebuild the zip whenever any entry content changes"],"tags":["zip","archive-parsing","compression","corrupt-file"],"backgroundTag":"checksum-mismatch","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}