zeroclaw-labs/zeroclaw · error · anyhow::Error

plugin archive exceeds extracted size limit of {max_extracte

Error message

plugin archive exceeds extracted size limit of {max_extracted_bytes} bytes

What it means

Zip-bomb defense part 1: before writing each file, extract_zip_safe_with_limit sums the entry's declared size (file.size(), saturating_add) against max_extracted_bytes and bails if the cumulative extracted total would exceed the cap. It relies on the zip header's declared uncompressed size.

Source

Thrown at src/plugin_registry.rs:192

where
    R: Read + Seek,
{
    let mut archive = zip::ZipArchive::new(reader)?;
    std::fs::create_dir_all(dest)?;
    let mut extracted_bytes = 0_u64;
    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let enclosed = enclosed_zip_path(file.name(), &file)?;
        let out_path = dest.join(enclosed);
        if file.is_dir() {
            std::fs::create_dir_all(&out_path)?;
            continue;
        }
        let Some(parent) = out_path.parent() else {
            bail!("plugin archive entry has no parent: {}", file.name());
        };
        if extracted_bytes.saturating_add(file.size()) > max_extracted_bytes {
            bail!("plugin archive exceeds extracted size limit of {max_extracted_bytes} bytes");
        }
        std::fs::create_dir_all(parent)?;
        let mut out = File::create(&out_path)?;
        copy_zip_entry_capped(
            &mut file,
            &mut out,
            &mut extracted_bytes,
            max_extracted_bytes,
        )?;
    }
    Ok(dest.to_path_buf())
}

fn enclosed_zip_path<R>(raw_name: &str, file: &zip::read::ZipFile<'_, R>) -> Result<PathBuf>
where
    R: Read,
{
    if is_unsafe_zip_entry_name(raw_name) {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Slim the plugin: move large assets out of the zip and download them at runtime from their own source
  2. If assets are legitimately needed, compress less aggressively irrelevant — the cap is on extracted bytes, so the only fix is fewer/smaller files
  3. Verify with `unzip -v plugin.zip` which entries have the largest uncompressed sizes and prune them
  4. Raise max_extracted_bytes only if you control the build and accept the disk-exhaustion risk

Example fix

# before: bundle includes 5GB model.bin -> extracted total over cap
# after: ship plugin without the asset
# manifest fetches it at first run from https://models.example/p.model
zip -r p.zip . -x 'assets/model.bin'
Defensive patterns

Strategy: validation

Validate before calling

// Sum declared uncompressed sizes before extracting (same policy as the guard):
let total: u64 = (0..archive.len()).map(|i| archive.by_index(i).unwrap().size()).sum();
if total > max_extracted_bytes { anyhow::bail!("archive would exceed extraction cap"); }

Try / catch

// On the extracted-size bail, inspect `unzip -v` for the largest entries and
// republish without them; do not raise the cap reflexively.

Prevention

When it happens

Trigger: Installing a plugin whose archive declares entries that individually or cumulatively exceed the extracted-size cap — e.g. a highly compressed archive that expands to gigabytes, or simply a plugin bundling oversized assets. The companion streaming check (error 1417) catches lying headers.

Common situations: Plugin bundles large model files, datasets, or static assets; upstream lowered the extraction cap in a fork; a crafted zip bomb aimed at exhausting disk during install.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/463bcb14bbeb8a48. Report an issue: GitHub.