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
- 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
- Inspect the zip locally: `unzip -l bundle.zip | grep -E '\.\.|^/'` to list offending entry names and confirm tampering vs. tooling artifact
- If you produce the zip yourself (internal marketplace mirror), repack with relative paths only (cd into the root and zip from there)
- 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
- Always extract through safe_zip_path (or equivalent component-whitelist) — never raw names
- Download only from the pinned official OPENAI_PLUGINS_ZIP_URL over trusted network paths
- When producing zips, pack from inside the root so all components are relative and Normal
- Quarantine and report artifacts that attempt traversal instead of retrying them
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
- zip entry has empty path
- openai/plugins marketplace download is too large: {} bytes
- downloaded openai/plugins marketplace is invalid
- downloaded openai/plugins marketplace root mismatch
- embedded official remote plugin marketplace is invalid
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/e75f76bc7f9d598c.
Report an issue: GitHub.