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

plugin archive sha256 mismatch

Error message

plugin archive sha256 mismatch

What it means

Integrity check failure: the SHA-256 of the downloaded archive bytes does not match the digest recorded in the registry entry (after stripping an optional 'sha256:' prefix, compared case-insensitively). verify_sha256_if_present only runs when the entry supplies a digest — no digest means no check.

Source

Thrown at src/plugin_registry.rs:157

    Ok(bytes)
}

fn append_chunk_capped(bytes: &mut Vec<u8>, chunk: &[u8], max_bytes: usize) -> Result<()> {
    if bytes.len().saturating_add(chunk.len()) > max_bytes {
        bail!("plugin archive exceeds maximum size of {max_bytes} bytes");
    }
    bytes.extend_from_slice(chunk);
    Ok(())
}

fn verify_sha256_if_present(bytes: &[u8], expected: Option<&str>) -> Result<()> {
    let Some(expected) = expected else {
        return Ok(());
    };
    let expected = expected.strip_prefix("sha256:").unwrap_or(expected);
    let actual = hex::encode(Sha256::digest(bytes));
    if !actual.eq_ignore_ascii_case(expected) {
        bail!("plugin archive sha256 mismatch");
    }
    Ok(())
}

pub(crate) fn extract_zip_safe<R>(reader: R, dest: &Path) -> Result<PathBuf>
where
    R: Read + Seek,
{
    extract_zip_safe_with_limit(reader, dest, MAX_PLUGIN_EXTRACTED_BYTES)
}

fn extract_zip_safe_with_limit<R>(
    reader: R,
    dest: &Path,
    max_extracted_bytes: u64,
) -> Result<PathBuf>
where
    R: Read + Seek,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Re-fetch the registry index — if it is stale it references an old digest, and a fresh index matches the current artifact
  2. If you publish the plugin, recompute sha256sum of the artifact and republish the entry with it in the same change
  3. Verify manually: sha256sum downloaded.zip vs the entry's value to distinguish corruption from mismatch
  4. If both index and artifact agree yet it still fails, suspect a truncating proxy; download from a trusted network and compare

Example fix

# before
# entry: sha256 = "aa11..." but artifact bytes hash to "bb22..."
# after
sha256sum p-0.3.0.zip          # bb22...
# update index entry to "bb22..." (or re-upload the aa11... artifact) and retry
Defensive patterns

Strategy: retry

Validate before calling

// Pre-verify before install when you control the pipeline:
let digest = hex::encode(Sha256::digest(&archive_bytes));
if !digest.eq_ignore_ascii_case(entry_sha256.trim_start_matches("sha256:")) {
    anyhow::bail!("digest mismatch before install");
}

Type guard

fn digest_matches(actual_hex: &str, expected: &str) -> bool {
    actual_hex.eq_ignore_ascii_case(expected.trim_start_matches("sha256:"))
}

Try / catch

// On sha256 mismatch: refresh the registry index once and retry (stale index
// is the common cause). A second identical failure means the artifact itself
// changed — stop and report to the publisher; do not bypass the check.

Prevention

When it happens

Trigger: Calling plugin install where the registry entry has a sha256 field but the artifact at the URL has different bytes: artifact was rebuilt/overwritten without updating the index, truncated/corrupted download, or the URL now serves different content.

Common situations: Publisher bumped the artifact but forgot to regenerate the digest; CDN cache serving an older artifact than the index references; a genuinely tampered/replaced artifact (the case this guard exists for).

Related errors


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