{"record":{"id":"198de921c4ffb094","repo":"astrid-runtime/astrid","slug":"release-executable-is-redirected-or-not-regular","errorCode":null,"errorMessage":"release executable is redirected or not regular: {name}","messagePattern":"release executable is redirected or not regular: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":859,"sourceCode":"        if !matches!(components.next(), Some(Component::Normal(_)))\n            || components.next().is_some()\n            || !unique.insert(*name)\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"invalid or duplicate executable name '{name}'\"),\n            ));\n        }\n\n        let source = extract_dir.join(name);\n        let metadata = std::fs::symlink_metadata(&source).map_err(|error| {\n            io::Error::new(\n                error.kind(),\n                format!(\"release archive is missing '{name}': {error}\"),\n            )\n        })?;\n        if metadata.file_type().is_symlink() || !metadata.is_file() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"release executable is redirected or not regular: {name}\"),\n            ));\n        }\n    }\n    Ok(())\n}\n\n#[cfg(not(windows))]\nfn replace_executable_set_by_rename(\n    install_dir: &Path,\n    extract_dir: &Path,\n    names: &[&str],\n) -> io::Result<()> {\n    let mut backups = Vec::new();\n    for name in names {\n        let live = install_dir.join(name);\n        if live.exists() {","sourceCodeStart":841,"sourceCodeEnd":877,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L841-L877","documentation":"For each executable, `validate_replacement_inputs` inspects `symlink_metadata` of the staged source. If the entry is a symlink or not a regular file (directory, FIFO, device, etc.), the call fails with `InvalidInput` — the library refuses to install executables that are redirects, which could silently point installations at attacker-controlled or unintended targets.","triggerScenarios":"The extracted archive contains a symlink (common on Unix for versioned binaries like `astrid -> astrid-1.2.3`), or `extract_dir.join(name)` resolves to a directory/pipe rather than a regular file; also when an extraction step preserved symlinks from a tarball.","commonSituations":"Unix archives (.tar.gz) built with symlinks inside; a malicious or mis-built release archive; extraction tools that create wrapper symlinks; a directory in extract_dir sharing the executable's name.","solutions":["Extract the archive with symlinks dereferenced/converted to real copies (e.g. `tar --dereference`) so each name is a regular file","Rebuild the release archive so executables are regular files, not symlinks","Point `extract_dir` at the real binary location, not a symlinked wrapper path; check each entry with `symlink_metadata(...).is_file()` before calling"],"exampleFix":"// before\nCommand::new(\"tar\").args([\"-xzf\", archive, \"-C\", extract_dir]).status()?;\n// after\nCommand::new(\"tar\")\n    .args([\"--dereference\", \"-xzf\", archive, \"-C\", extract_dir])\n    .status()?;","handlingStrategy":"validation","validationCode":"for name in names {\n    let md = std::fs::symlink_metadata(extract_dir.join(name))?;\n    if md.file_type().is_symlink() || !md.is_file() {\n        return Err(anyhow!(\"{name} is not a regular file\"));\n    }\n}","typeGuard":"fn is_regular_file_no_symlink(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|m| !m.file_type().is_symlink() && m.is_file())\n        .unwrap_or(false)\n}","tryCatchPattern":"match replace_executable_set(&install_dir, &extract_dir, names) {\n    Err(e) if e.to_string().starts_with(\"release executable is redirected\") => {\n        eprintln!(\"archive contains symlinks; re-extract with --dereference: {e}\");\n    }\n    other => other.map_err(Into::into),\n}","preventionTips":["Extract archives with symlink dereferencing enabled","Build release archives without internal symlinks","Use symlink_metadata, not metadata, when vetting sources","Treat non-regular files in archives as a packaging bug"],"tags":["security","symlink","validation","self-update"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}