{"record":{"id":"ec776213979574fa","repo":"BigPizzaV3/CodexPlusPlus","slug":"zip-entry-has-empty-path","errorCode":null,"errorMessage":"zip entry has empty path","messagePattern":"zip entry has empty path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/codex-plus-core/src/plugin_marketplace.rs","lineNumber":453,"sourceCode":"            .with_context(|| format!(\"failed to read zip entry {}\", file.name()))?;\n        std::fs::write(&output_path, contents)\n            .with_context(|| format!(\"failed to write {}\", output_path.display()))?;\n    }\n    Ok(())\n}\n\nfn safe_zip_path(name: &str) -> anyhow::Result<PathBuf> {\n    let path = Path::new(name);\n    let mut relative = PathBuf::new();\n    for component in path.components() {\n        match component {\n            Component::Normal(value) => relative.push(value),\n            Component::CurDir => {}\n            _ => anyhow::bail!(\"zip entry escapes destination: {name}\"),\n        }\n    }\n    if relative.as_os_str().is_empty() {\n        anyhow::bail!(\"zip entry has empty path\");\n    }\n    Ok(relative)\n}\n\nfn zip_entry_relative_path(name: &str) -> Option<PathBuf> {\n    let path = Path::new(name);\n    let mut components = path.components();\n    match components.next()? {\n        Component::Normal(_) => {}\n        _ => return None,\n    }\n    let mut relative = PathBuf::new();\n    for component in components {\n        match component {\n            Component::Normal(value) => relative.push(value),\n            Component::CurDir => {}\n            _ => return None,\n        }","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/plugin_marketplace.rs#L435-L471","documentation":"safe_zip_path (crates/codex-plus-core/src/plugin_marketplace.rs:453) rejects entry names that normalize to an empty relative path — e.g. the name '.', an empty string, or a run of only-current-dir components — after the escape check passes. An empty path has no file to write and would otherwise collapse into writing onto the destination directory itself, so extraction refuses it.","triggerScenarios":"Extracting a marketplace zip containing a directory entry named '.' or '' (or a sequence of '.' components) — typically produced by buggy archivers, hand-rolled zip writers, or deliberate malformed-archive fuzzing.","commonSituations":"Zips packed with a redundant './' prefix entry; archives created by scripts using zipfile with an accidental empty arcname; corrupted downloads where entry headers degrade to empty names.","solutions":["Rebuild the zip without degenerate entries: list with `unzip -l` and repack excluding '.'/empty names","Re-download the artifact from the official URL — empty-name entries often indicate corruption in transit","If writing the zip programmatically, skip directory markers and empty names when adding entries (zipfile: skip when arcname in ('', '.'))","Treat repeated occurrences as tampering and fall back to the embedded marketplace copy"],"exampleFix":"# python: before — accidentally adds empty-name entry\nzf.writestr('', b'')\n\n# after — skip degenerate names entirely\nif name in ('', '.'):\n    continue\nzf.writestr(name, data)","handlingStrategy":"validation","validationCode":"fn zip_names_valid(names: impl IntoIterator<Item = String>) -> bool {\n    names.into_iter().all(|name| {\n        let mut rel = std::path::PathBuf::new();\n        for c in std::path::Path::new(&name).components() {\n            match c {\n                std::path::Component::Normal(v) => rel.push(v),\n                std::path::Component::CurDir => {}\n                _ => return false,\n            }\n        }\n        !rel.as_os_str().is_empty()\n    })\n}","typeGuard":"fn entry_name_usable(name: &str) -> bool {\n    !name.trim().is_empty() && name != \".\"\n}","tryCatchPattern":"// Skip-and-continue for degenerate entries is acceptable when repacking your own artifacts;\n// for downloads, fail the install\nErr(e) if e.to_string() == \"zip entry has empty path\" => {\n    tracing::warn!(\"archive contains empty-name entry; treating artifact as malformed\");\n    redownload_and_retry_once().await?\n}","preventionTips":["When generating zips programmatically, skip '' and '.' entry names","Validate archives with `unzip -t` (and a name scan) before shipping or installing","Re-download on malformed-archive errors — empty names usually indicate corruption"],"tags":["rust","zip","malformed-archive","path-validation","plugin-marketplace"],"backgroundTag":"invalid-zip-entry-name","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}