BigPizzaV3/CodexPlusPlus · error · anyhow::Error

压缩包条目越界:

Error message

压缩包条目越界:{value}

What it means

safe_relative_path walks each zip entry's path components and accepts only Normal components (skipping CurDir). Any ParentDir, RootDir, or prefix component — i.e. an entry attempting to escape the destination — triggers this bail with the raw entry path, preventing zip-slip.

Solutions

  1. 不要安装含越界条目的包——这是安全信号,应拒绝该 zip 并考虑举报来源
  2. 若为自有打包产物,修正打包脚本,使条目均为相对路径且不含 .. 或绝对前缀
  3. 换用正常工具(如 git archive / 官方下载的 zip)重新获取压缩包
Defensive patterns

Strategy: try-catch

Try / catch

match state.install_from_zip(&zip_bytes, &repo_path) {
    Err(e) if e.to_string().contains("压缩包条目越界") => {
        eprintln!("zip 含越界条目(zip-slip),拒绝安装该包");
    }
    other => other?,
}

Prevention

When it happens

Trigger: zip 中存在形如 "../evil"、"/etc/passwd" 或带盘符前缀(C:\...)的条目名,extract_skill_subtree 对该条目调用 safe_relative_path 时抛出。

Common situations: 恶意构造的 zip-slip 攻击包;正常但打包方式怪异的 zip(条目带绝对路径);跨平台打包工具生成的含 Windows 前缀的条目。

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/83a6fc4a9da7d7b8. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/skills.rs:839

            .with_context(|| format!("写入 {} 失败", output_path.display()))?;
        if inner == SKILL_MANIFEST_FILE {
            wrote_manifest = true;
        }
    }

    if !wrote_manifest {
        anyhow::bail!("{repo_path} 下没有 SKILL.md,不是一个有效的 skill");
    }
    Ok(())
}

fn safe_relative_path(value: &str) -> anyhow::Result<PathBuf> {
    let mut relative = PathBuf::new();
    for component in Path::new(value).components() {
        match component {
            std::path::Component::Normal(part) => relative.push(part),
            std::path::Component::CurDir => {}
            _ => anyhow::bail!("压缩包条目越界:{value}"),
        }
    }
    if relative.as_os_str().is_empty() {
        anyhow::bail!("压缩包条目路径为空");
    }
    Ok(relative)
}

pub fn repo_zip_url(repo: &SkillRepo) -> String {
    format!(
        "https://codeload.github.com/{}/{}/zip/refs/heads/{}",
        repo.owner, repo.name, repo.branch
    )
}

pub fn repo_tree_url(repo: &SkillRepo) -> String {
    format!(
        "https://api.github.com/repos/{}/{}/git/trees/{}?recursive=1",

View on GitHub (pinned to b1ed92e5e4)