{"id":"c0662a46180a7ac3","repo":"rust-lang/cargo","slug":"invalid-tarball-downloaded-contains-a-file-at-en","errorCode":null,"errorMessage":"invalid tarball downloaded, contains a file at {entry_path:?} which isn't under {prefix:?}","messagePattern":"invalid tarball downloaded, contains a file at (.+?) which isn't under (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"src/sources/registry/mod.rs","lineNumber":988,"sourceCode":"    for entry in tar.entries()? {\n        let mut entry = entry.context(\"failed to iterate over archive\")?;\n        let entry_path = entry\n            .path()\n            .context(\"failed to read entry path\")?\n            .into_owned();\n\n        if let Ok(path) = entry_path.strip_prefix(prefix) {\n            if !include(path) {\n                continue;\n            }\n        } else {\n            // We're going to unpack this tarball into the global source\n            // directory, but we want to make sure that it doesn't accidentally\n            // (or maliciously) overwrite source code from other crates. Cargo\n            // itself should never generate a tarball that hits this error, and\n            // crates.io should also block uploads with these sorts of tarballs,\n            // but be extra sure by adding a check here as well.\n            anyhow::bail!(\n                \"invalid tarball downloaded, contains \\\n                     a file at {entry_path:?} which isn't under {prefix:?}\",\n            )\n        }\n\n        // Prevent unpacking symlinks and other unexpected entry types\n        match entry.header().entry_type() {\n            EntryType::Regular | EntryType::Directory => {}\n            t => anyhow::bail!(\n                \"invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}\",\n            ),\n        }\n\n        // Prevent unpacking the lockfile from the crate itself.\n        if entry_path\n            .file_name()\n            .map_or(false, |p| p == PACKAGE_SOURCE_LOCK)\n        {","sourceCodeStart":970,"sourceCodeEnd":1006,"githubUrl":"https://github.com/rust-lang/cargo/blob/0e07a155371a6ce88ae53a2c00df940280c09a67/src/sources/registry/mod.rs#L970-L1006","documentation":"While unpacking a downloaded `.crate` tarball (src/sources/registry/mod.rs:977), Cargo requires every archive entry's path to live under the expected package prefix (`<name>-<version>/`). An entry that doesn't `strip_prefix(prefix)` is rejected as an invalid/malicious tarball — this is the path-traversal / wrong-packaging guard. The comment notes Cargo itself and crates.io should never produce such tarballs; this check is a belt-and-suspenders defense.","triggerScenarios":"A tarball containing an entry whose path doesn't start with `<name>-<version>/` — e.g. an absolute path (`/etc/...`), a `../` traversal, or a flat entry with no prefix. Produced by a buggy custom registry/packaging tool, a corrupted download, or an attacker-controlled tarball.","commonSituations":"A private registry whose packaging tool doesn't prepend the `name-version/` prefix; a corrupted/garbled download that scrambled entry paths; a malicious substitute registry. Should never happen for crates.io-published crates.","solutions":["Re-create/re-download the tarball: clear `~/.cargo/registry/cache/<reg>/<pkg>-<ver>.crate` and refetch.","If you publish/maintain the crate, ensure your packaging produces entries under `<name>-<version>/` (standard `cargo package` output).","Inspect the tarball manually (`tar -tvf <file>.crate`) to see the offending entry path and fix the upstream packaging.","Treat as a potential supply-chain attack if the registry is untrusted — stop using that registry."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate a tarball before unpacking: every entry must live under the prefix.\nfn tarball_has_valid_prefix<R: Read>(tar: &mut tar::Archive<R>, prefix: &str) -> bool {\n    for e in tar.entries().ok().into_iter().flatten() {\n        if !e.path().ok().map_or(false, |p| p.starts_with(prefix)) { return false; }\n    }\n    true\n}","typeGuard":"pub fn entry_under_prefix(p: &std::path::Path, prefix: &str) -> bool {\n    p.strip_prefix(prefix).is_ok()\n}","tryCatchPattern":null,"preventionTips":["Always package crates with `cargo package`, which guarantees the `<name>-<ver>/` prefix.","Refuse to consume tarballs that fail the prefix check — treat as supply-chain risk.","Verify downloaded `.crate` checksums before unpacking (Cargo does this upstream)."],"tags":["cargo","registry","tarball","security","path-traversal","validation"],"analyzedSha":"0e07a155371a6ce88ae53a2c00df940280c09a67","analyzedAt":"2026-08-06T01:46:58.334Z","schemaVersion":2}