{"record":{"id":"d33546534f8dd486","repo":"astrid-runtime/astrid","slug":"malicious-shuttle-detected-invalid-path","errorCode":null,"errorMessage":"malicious shuttle detected: invalid path '{}'","messagePattern":"malicious shuttle detected: invalid path '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-cli/src/commands/distro/shuttle.rs","lineNumber":188,"sourceCode":"    let tar = flate2::read::GzDecoder::new(tar_gz);\n    let mut archive = tar::Archive::new(tar);\n\n    for entry in archive\n        .entries()\n        .context(\"failed to read shuttle entries (truncated or not a gzip tar?)\")?\n    {\n        let mut entry = entry.context(\"failed to read shuttle entry (truncated archive?)\")?;\n        let entry_path = entry\n            .path()\n            .context(\"invalid path in shuttle\")?\n            .into_owned();\n\n        if entry_path.is_absolute()\n            || entry_path\n                .components()\n                .any(|c| matches!(c, Component::ParentDir))\n        {\n            bail!(\n                \"malicious shuttle detected: invalid path '{}'\",\n                entry_path.display()\n            );\n        }\n\n        let et = entry.header().entry_type();\n        if et.is_symlink() || et.is_hard_link() {\n            bail!(\n                \"malicious shuttle detected: links are not allowed ('{}')\",\n                entry_path.display()\n            );\n        }\n        // Skip directory entries — parents are created as needed below.\n        if et.is_dir() {\n            continue;\n        }\n        // Everything that survives to here must be an ordinary file. Device\n        // nodes, FIFOs, sockets, and any other special entry type are","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/shuttle.rs#L170-L206","documentation":"`unpack` validates each archive entry's path before writing anything to disk, rejecting absolute paths and any path containing `..` components. This is a zip-slip/path-traversal defense: a crafted `.shuttle` could otherwise write files outside the extraction directory.","triggerScenarios":"Calling `shuttle::unpack` on an archive whose member header path is absolute (e.g. `/etc/passwd`) or contains a parent-directory component (e.g. `../../evil`). Detected by the malicious-entry tests.","commonSituations":"Installing a shuttle downloaded from an untrusted source; an archive produced by a buggy or tampered packer that stored raw absolute paths.","solutions":["Do not install this shuttle — treat it as malicious or corrupt and obtain it from a trusted source.","Re-pack the capsule with relative, `..`-free paths using the project's `pack` command.","If you control the producer, fix the packing code to sanitize member paths (e.g. strip leading `/` and reject `..`)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn entry_path_is_safe(p: &str) -> bool {\n    let path = std::path::Path::new(p);\n    !path.is_absolute()\n        && !path.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}","typeGuard":"fn is_safe_member(p: &Path) -> bool {\n    p.is_relative() && !p.components().any(|c| matches!(c, Component::ParentDir))\n}","tryCatchPattern":"if let Err(e) = shuttle::unpack(archive, dest) {\n    if e.to_string().contains(\"invalid path\") {\n        eprintln!(\"rejecting malicious archive: {e}\");\n        // do not retry; discard the archive\n    }\n}","preventionTips":["Only install capsules from trusted, signature-verified sources","Inspect archive member names (`tar tf`) before unpacking unknown archives","Prefer the official pack command over hand-built tars","Keep the unpack sandboxed in a temp dir"],"tags":["security","path-traversal","archive","unpack"],"backgroundTag":"path-traversal-blocked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}