farion1231/cc-switch · error · anyhow::Error

ARCHIVE_TOO_LARGE

ARCHIVE_TOO_LARGE

Error message

{"code":"ARCHIVE_TOO_LARGE","context":{"limit_mb":"{limit_mb}"},"suggestion":"checkZipContent"}

What it means

Structured error from download_and_extract: the streamed response body exceeded MAX_ARCHIVE_DOWNLOAD_BYTES (128 MiB, skill.rs:327) while being read chunk-by-chunk into memory. This limit exists because the whole archive is buffered as a Vec<u8> before ZipArchive sees it — without the cap a multi-GB body would exhaust the heap before the extraction budget ever engages. Content-Length is deliberately not trusted; only bytes actually received are counted.

Source

Thrown at src-tauri/src/services/skill.rs:3148

                &[("status", &status)],
                match status.as_str() {
                    "403" => Some("http403"),
                    "404" => Some("http404"),
                    "429" => Some("http429"),
                    _ => Some("checkNetwork"),
                },
            )));
        }

        // 逐块读并卡住压缩体大小:`response.bytes()` 会先把攻击者控制的整个归档
        // 收进内存,之后才轮到 ZipArchive 和解压预算——那时候堆已经被吃光了。
        // 不能只信 Content-Length(可以撒谎或缺失),必须按实际收到的字节数算。
        let mut response = response;
        let mut body: Vec<u8> = Vec::new();
        while let Some(chunk) = response.chunk().await? {
            if body.len().saturating_add(chunk.len()) as u64 > MAX_ARCHIVE_DOWNLOAD_BYTES {
                let limit_mb = (MAX_ARCHIVE_DOWNLOAD_BYTES / 1024 / 1024).to_string();
                return Err(anyhow::anyhow!(format_skill_error(
                    "ARCHIVE_TOO_LARGE",
                    &[("limit_mb", &limit_mb)],
                    Some("checkZipContent"),
                )));
            }
            body.extend_from_slice(&chunk);
        }

        let cursor = std::io::Cursor::new(body);
        let archive = zip::ZipArchive::new(cursor)?;
        Self::extract_repo_archive(archive, dest)
    }

    /// 按预算把单个归档条目写出,累计超限即中止。
    ///
    /// 逐块累加而非读取归档头里声明的 size:那个值由归档作者填写,压缩炸弹会撒谎。
    fn copy_entry_within_budget<R: std::io::Read, W: std::io::Write>(
        reader: &mut R,

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Pick a leaner repo or a fork with assets stripped (git filter-repo / BFG) and add that instead
  2. Download the skill files manually and import them as a local ZIP — but note the local path enforces the same entry/byte budgets
  3. Only in a fork: raise MAX_ARCHIVE_DOWNLOAD_BYTES, understanding it raises peak memory usage of the app
Defensive patterns

Strategy: validation

Validate before calling

// Rust — pre-flight size check via the GitHub API (size is in KB)
async fn repo_too_big(owner: &str, name: &str) -> Result<bool> {
    let url = format!("https://api.github.com/repos/{owner}/{name}");
    let resp: serde_json::Value = crate::proxy::http_client::get().get(url).send().await?.json().await?;
    Ok(resp["size"].as_u64().unwrap_or(0) > 128 * 1024) // conservative proxy for archive size
}

Type guard

export function isArchiveTooLarge(e: unknown): boolean {
  return typeof e === "string" && e.includes('"code":"ARCHIVE_TOO_LARGE"');
}

Try / catch

match download_repo(&repo).await {
    Err(e) if e.to_string().contains("ARCHIVE_TOO_LARGE") => {
        // suggest a leaner repo or manual local import; do NOT retry — the size will not shrink
    }
    other => other,
}

Prevention

When it happens

Trigger: Discovering/downloading a repo whose zip archive exceeds 128 MiB — typically repos bundling binaries, media, or vendored dependencies; or a malicious repo deliberately shipping a huge body (repos can be added via deeplink).

Common situations: Monorepos with assets; users expecting any GitHub repo to be installable; the doc comment notes skill repos are markdown, so 128 MiB is far beyond normal.

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/14c268939ea5d642. Report an issue: GitHub.