BigPizzaV3/CodexPlusPlus · critical

zip entry escapes destination: {name}

Error message

zip entry escapes destination: {name}

What it means

safe_zip_path (crates/codex-plus-core/src/plugin_marketplace.rs:442) sanitizes every entry name before extraction of the marketplace zip: it keeps only Component::Normal parts, collapses CurDir ('.'), and rejects anything else — ParentDir ('..'), root separators ('/'), and Windows prefixes ('C:\') — with 'zip entry escapes destination: {name}'. This is a zip-slip defense: without it, an entry like ../../.ssh/authorized_keys would write outside the destination directory.

Source

Thrown at crates/codex-plus-core/src/plugin_marketplace.rs:449

                .with_context(|| format!("failed to create {}", parent.display()))?;
        }
        let mut contents = Vec::new();
        file.read_to_end(&mut contents)
            .with_context(|| format!("failed to read zip entry {}", file.name()))?;
        std::fs::write(&output_path, contents)
            .with_context(|| format!("failed to write {}", output_path.display()))?;
    }
    Ok(())
}

fn safe_zip_path(name: &str) -> anyhow::Result<PathBuf> {
    let path = Path::new(name);
    let mut relative = PathBuf::new();
    for component in path.components() {
        match component {
            Component::Normal(value) => relative.push(value),
            Component::CurDir => {}
            _ => anyhow::bail!("zip entry escapes destination: {name}"),
        }
    }
    if relative.as_os_str().is_empty() {
        anyhow::bail!("zip entry has empty path");
    }
    Ok(relative)
}

fn zip_entry_relative_path(name: &str) -> Option<PathBuf> {
    let path = Path::new(name);
    let mut components = path.components();
    match components.next()? {
        Component::Normal(_) => {}
        _ => return None,
    }
    let mut relative = PathBuf::new();
    for component in components {
        match component {

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Do not whitelist or patch the sanitizer — treat the error as the artifact being untrusted: re-download from the official OPENAI_PLUGINS_ZIP_URL over a clean network path and verify the artifact source
  2. Inspect the zip locally: `unzip -l bundle.zip | grep -E '\.\.|^/'` to list offending entry names and confirm tampering vs. tooling artifact
  3. If you produce the zip yourself (internal marketplace mirror), repack with relative paths only (cd into the root and zip from there)
  4. Keep the embedded marketplace copy as fallback and report the bad artifact upstream

Example fix

# before: packing with absolute roots produces escaping entries
cd / && zip -r /tmp/marketplace.zip /home/user/.agents/plugins  # entries like home/user/...

# after: pack from inside the root so all components are Normal
cd ~/.agents/plugins && zip -r /tmp/marketplace.zip .
Defensive patterns

Strategy: validation

Validate before calling

// Scan entries before extraction (mirror of safe_zip_path)
fn zip_is_safe(names: impl IntoIterator<Item = String>) -> bool {
    names.into_iter().all(|name| {
        std::path::Path::new(&name).components().all(|c| matches!(c,
            std::path::Component::Normal(_) | std::path::Component::CurDir))
    })
}
assert!(zip_is_safe(zip.file_names().map(str::to_string)));

Type guard

fn entry_is_safe(name: &str) -> bool {
    !name.contains("..") && !name.starts_with('/') && !name.contains(":\\")
        && !name.replace('\\', "/").split('/').any(|p| p == "..")
}

Try / catch

// Never bypass: a hit means untrusted artifact
match install_openai_plugins_zip(home, &bytes) {
    Err(e) if e.to_string().contains("escapes destination") => {
        tracing::error!("zip-slip attempt detected; discarding download");
        quarantine_download(&bytes); // keep for forensics, do not install
        return Err(e.context("untrusted marketplace artifact"));
    }
    rest => rest?,
}

Prevention

When it happens

Trigger: Installing the openai/plugins marketplace zip (install path calls safe_zip_path at plugin_marketplace.rs:422) when any entry name contains '..' or an absolute/prefixed path — i.e. a maliciously crafted zip, a zip built with absolute paths, or one packed by a tool that emits leading slashes.

Common situations: Supply-chain attack or tampered artifact where the zip tries to escape; zips created with `zip -r /abs/path` style absolute names; test fixtures hand-writing entry names with '..'; a compromised or misconfigured CDN serving a doctored bundle.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/e75f76bc7f9d598c. Report an issue: GitHub.