{"record":{"id":"8893b3583b4af0da","repo":"zeroclaw-labs/zeroclaw","slug":"plugin-archive-exceeds-maximum-size-of-max-bytes","errorCode":null,"errorMessage":"plugin archive exceeds maximum size of {max_bytes} bytes","messagePattern":"plugin archive exceeds maximum size of (.+?) bytes","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/plugin_registry.rs","lineNumber":144,"sourceCode":"    }\n    Ok(bytes)\n}\n\npub(crate) fn collect_capped_chunks<I>(chunks: I, max_bytes: usize) -> Result<Vec<u8>>\nwhere\n    I: IntoIterator<Item = Result<Vec<u8>>>,\n{\n    let mut bytes = Vec::new();\n    for chunk in chunks {\n        let chunk = chunk?;\n        append_chunk_capped(&mut bytes, &chunk, max_bytes)?;\n    }\n    Ok(bytes)\n}\n\nfn append_chunk_capped(bytes: &mut Vec<u8>, chunk: &[u8], max_bytes: usize) -> Result<()> {\n    if bytes.len().saturating_add(chunk.len()) > max_bytes {\n        bail!(\"plugin archive exceeds maximum size of {max_bytes} bytes\");\n    }\n    bytes.extend_from_slice(chunk);\n    Ok(())\n}\n\nfn verify_sha256_if_present(bytes: &[u8], expected: Option<&str>) -> Result<()> {\n    let Some(expected) = expected else {\n        return Ok(());\n    };\n    let expected = expected.strip_prefix(\"sha256:\").unwrap_or(expected);\n    let actual = hex::encode(Sha256::digest(bytes));\n    if !actual.eq_ignore_ascii_case(expected) {\n        bail!(\"plugin archive sha256 mismatch\");\n    }\n    Ok(())\n}\n\npub(crate) fn extract_zip_safe<R>(reader: R, dest: &Path) -> Result<PathBuf>","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/src/plugin_registry.rs#L126-L162","documentation":"The streaming backstop of the archive size cap: while reading the response body chunk by chunk, append_chunk_capped bails as soon as accumulated bytes (plus the incoming chunk) would exceed max_bytes. It catches servers that lie about (or omit) Content-Length, which the header check (error 1409) cannot see.","triggerScenarios":"Downloading a plugin archive where Content-Length is missing or understated, and the actual streamed body exceeds MAX_PLUGIN_ZIP_BYTES. Reached from download_archive_bytes' chunk loop (and reusable via collect_capped_chunks).","commonSituations":"Chunked transfer-encoding with no Content-Length from the artifact host; a proxy/CDN rewriting headers; a malicious or misconfigured server deliberately under-reporting size to slip a large payload through.","solutions":["Treat it as the cap working as designed: the remote is sending more bytes than it declared (or nothing at all)","Fetch the URL manually (curl -L <url> -o /dev/null -w '%{size_download}') to see the true size","Re-publish a smaller artifact, or point the entry at a host that reports accurate Content-Length so the header check can fail fast","Raise the limit only if the artifact legitimately needs to be larger and you accept the memory implications"],"exampleFix":"# before: server sends chunked body of 2x the cap with no Content-Length\n# after: serve the artifact with an accurate header\ncurl -I https://cdn/p.zip   # must show content-length < MAX_PLUGIN_ZIP_BYTES","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Stream the body yourself with the same saturating cap if you need custom UX:\nlet mut total = 0usize;\nwhile let Some(chunk) = resp.chunk().await? {\n    total = total.saturating_add(chunk.len());\n    if total > MAX_PLUGIN_ZIP_BYTES { return Err(anyhow::anyhow!(\"over cap\")); }\n    buf.extend_from_slice(&chunk);\n}","preventionTips":["Serve artifacts from hosts that report accurate Content-Length","Never retry an over-cap download unchanged — it will fail again","Treat repeated header/stream cap disagreement as a tampering signal"],"tags":["plugin-registry","size-limit","streaming","security"],"backgroundTag":"download-size-limit-exceeded","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}