{"record":{"id":"98e4143dff5c99a0","repo":"astrid-runtime/astrid","slug":"malicious-shuttle-detected-links-are-not-allowed","errorCode":null,"errorMessage":"malicious shuttle detected: links are not allowed ('{}')","messagePattern":"malicious shuttle detected: links are not allowed \\('(.+?)'\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-cli/src/commands/distro/shuttle.rs","lineNumber":196,"sourceCode":"        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\n        // rejected: a `.shuttle` only ever legitimately carries regular\n        // files, so an exotic type is either corruption or an attack.\n        if !et.is_file() {\n            bail!(\n                \"malicious shuttle detected: unsupported entry type for '{}' \\\n                 (only regular files are allowed)\",\n                entry_path.display()\n            );","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/shuttle.rs#L178-L214","documentation":"`unpack` rejects any archive member whose tar entry type is a symlink or hard link. Links could redirect writes to arbitrary filesystem locations, so a `.shuttle` may only carry regular files; any link entry is treated as tampering or corruption.","triggerScenarios":"Calling `shuttle::unpack` on an archive containing a symlink (`et.is_symlink()`) or hard-link (`et.is_hard_link()`) entry, e.g. one built with the system `tar` that included links.","commonSituations":"Building a capsule by hand with `tar cf` from a directory that contains symlinks (common in node_modules or build output), or a maliciously crafted archive.","solutions":["Re-pack using the project's `pack` command, after removing or dereferencing symlinks in the staged directory.","Use `tar --dereference` equivalents: copy the tree with `cp -rL` so links become regular files, then pack.","Do not install untrusted archives that contain links."],"exampleFix":"// before\n$ tar cf capsule.shuttle staged/        # includes symlinks\n// after\n$ cp -rL staged/ staged-flat/           # dereference links\n$ astrid distro pack ...                # repack with regular files","handlingStrategy":"validation","validationCode":"// before unpacking an unknown archive, inspect entry types\n// tar -tvf x.shuttle | grep -E '\\->|^h'   # links present?\nfn archive_has_links(member_types: &[&str]) -> bool {\n    member_types.iter().any(|t| t.contains(\"->\"))\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = shuttle::unpack(archive, dest) {\n    if e.to_string().contains(\"links are not allowed\") {\n        eprintln!(\"archive contains links; repack with dereferenced files: {e}\");\n    }\n}","preventionTips":["Stage capsules from directories without symlinks (use `cp -rL`)","Never hand-build capsules with raw tar over build output","Treat link-containing archives as untrusted"],"tags":["security","symlink","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"}