{"record":{"id":"ee474943cf4ec102","repo":"Hmbown/CodeWhale","slug":"the-update-service-exceeded-its-response-size-limit","errorCode":null,"errorMessage":"The update service exceeded its response size limit.","messagePattern":"The update service exceeded its response size limit\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":25,"sourceCode":"import { inflateRawSync } from \"node:zlib\";\nimport { replaceMacBundle, verifyReleaseBundle } from \"./install-macos.mjs\";\nimport { APP_VERSION, APP_NAME } from \"../src/app-socket.mjs\";\nimport { stateDir } from \"../src/registry.mjs\";\n\nconst repository=\"https://github.com/Hmbown/codewhale-cu-plugin\";\nconst limit=256*1024*1024;\nconst updateResultPath=()=>path.join(stateDir(),\"update-result.json\");\nexport function readUpdateResult() {\n  try {\n    if(fs.statSync(updateResultPath()).size>4096) return null;\n    const result=JSON.parse(fs.readFileSync(updateResultPath(),\"utf8\"));\n    if(typeof result.ok!==\"boolean\"||typeof result.message!==\"string\"||result.message.length>1000) return null;\n    return {available:false,message:result.message};\n  } catch { return null; }\n}\nasync function responseBytes(response, maximum) {\n  const chunks=[]; let size=0;\n  for await(const chunk of response.body) { size+=chunk.length; if(size>maximum) throw new Error(\"The update service exceeded its response size limit.\"); chunks.push(chunk); }\n  return Buffer.concat(chunks);\n}\nexport function newerVersion(candidate,current) {\n  const parse=value=>/^\\d+\\.\\d+\\.\\d+$/.test(value)?value.split(\".\").map(Number):null;\n  const a=parse(candidate),b=parse(current); if(!a||!b) return false;\n  for(let i=0;i<3;i++) { if(a[i]!==b[i]) return a[i]>b[i]; } return false;\n}\nexport function releaseUpdate(release,current=APP_VERSION) {\n  const version=release?.tag_name?.replace(/^v/,\"\");\n  if(!version||release.draft||release.prerelease||!newerVersion(version,current)) return {available:false,message:`You have Computer Use ${current}. No newer stable installer is available.`};\n  const name=`Codewhale-Computer-Use-${version}-macos-universal.zip`;\n  const asset=release.assets?.find(asset=>asset.name===name);\n  const url=`${repository}/releases/download/v${version}/${name}`;\n  if(!asset||asset.browser_download_url!==url||!/^sha256:[a-f0-9]{64}$/.test(asset.digest)||!Number.isSafeInteger(asset.size)||asset.size<=0||asset.size>limit) return {available:false,message:`Version ${version} has no verified macOS installer yet.`};\n  return {available:true,version,url,sha256:asset.digest.slice(7),size:asset.size,message:`Computer Use ${version} is available. Install it to restart the helper; existing computer sessions will stop.`};\n}\nexport async function checkForUpdate() {\n  const response=await fetch(\"https://api.github.com/repos/Hmbown/codewhale-cu-plugin/releases/latest\",{redirect:\"error\",headers:{Accept:\"application/vnd.github+json\",\"X-GitHub-Api-Version\":\"2022-11-28\"},signal:AbortSignal.timeout(10_000)});","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L7-L43","documentation":"responseBytes streams an HTTP response body while enforcing a maximum byte size; when the accumulated size exceeds the limit (1 MiB for the release metadata check), it throws immediately without buffering the rest. This protects against a malicious or malfunctioning update service returning an oversized payload.","triggerScenarios":"checkForUpdate receiving a GitHub API response larger than 1 MiB — e.g. a release with thousands of assets, a proxy injecting content, or a hostile response impersonating the API.","commonSituations":"Extremely large release metadata; corporate proxy or captive portal returning bloated HTML instead of JSON; attacker-controlled response in a MitM scenario.","solutions":["Retry later — the official release metadata should be well under 1 MiB, so this usually indicates a transient or intermediary problem","Check for proxies/VPNs intercepting api.github.com and returning injected content","If releases legitimately grow, raise the 1024*1024 maximum passed to responseBytes"],"exampleFix":"// before (fixed 1 MiB cap)\nreturn releaseUpdate(JSON.parse((await responseBytes(response, 1024 * 1024)).toString(\"utf8\")));\n// after (raise cap if release metadata legitimately grows)\nreturn releaseUpdate(JSON.parse((await responseBytes(response, 4 * 1024 * 1024)).toString(\"utf8\")));","handlingStrategy":"try-catch","validationCode":"const cl = response.headers.get(\"content-length\");\nif (cl && Number(cl) > 1024 * 1024) throw new Error(\"release metadata too large\");","typeGuard":null,"tryCatchPattern":"try {\n  await checkForUpdate();\n} catch (e) {\n  if (e.message.includes(\"response size limit\")) {\n    log.warn(\"Update service returned oversized payload; skipping check\");\n  } else throw e;\n}","preventionTips":["Keep release metadata lean (few assets, no giant release notes)","Inspect proxy/VPN behavior that may inflate API responses","Raise the size cap deliberately if release metadata legitimately grows","Prefer authenticated, pinned API endpoints over intercepting proxies"],"tags":["network","http","payload-too-large","security"],"backgroundTag":"payload-too-large","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"}