{"record":{"id":"7454ce645b15a5a6","repo":"astrid-runtime/astrid","slug":"legacy-state-source-contains-a-redirect","errorCode":null,"errorMessage":"legacy state source contains a redirect: {}","messagePattern":"legacy state source contains a redirect: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_retirement.rs","lineNumber":62,"sourceCode":"            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is redirected: {}\", path.display()),\n        ));\n    }\n    if !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy state source is not a directory: {}\", path.display()),\n        ));\n    }\n    crate::platform_fs::verify_no_redirects(path)?;\n    ensure_legacy_tree_boundary(path, root_device, &metadata)?;\n\n    for entry in std::fs::read_dir(path)? {\n        let entry = entry?;\n        let child = entry.path();\n        let child_metadata = std::fs::symlink_metadata(&child)?;\n        if child_metadata.file_type().is_symlink() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"legacy state source contains a redirect: {}\",\n                    child.display()\n                ),\n            ));\n        }\n        ensure_legacy_tree_boundary(&child, root_device, &child_metadata)?;\n        if child_metadata.is_dir() {\n            validate_legacy_tree(&child, root_device)?;\n        } else if child_metadata.is_file() {\n            // Opening only after the no-follow validation ensures a replaced\n            // symlink is rejected rather than read or removed through it.\n            crate::platform_fs::verify_no_redirects(&child)?;\n        } else {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_retirement.rs#L44-L80","documentation":"Thrown when an entry inside the legacy state source tree is a symlink. The retirement path is strictly no-follow: every child is checked with symlink_metadata before being validated or deleted, and any redirect is rejected so deletion can never act through a link onto an outside target.","triggerScenarios":"validate_legacy_tree enumerates the tree and finds child_metadata.file_type().is_symlink() for any child — e.g. a symlinked data file (sstables, wal, manifest), a linked subdirectory, or a convenience link created by tooling inside the legacy state directory.","commonSituations":"Administrators symlinking large WAL/SSTable files onto another volume to save space; moving part of the tree and linking it back; backup tools leaving symlink placeholders; restore from a layout that used links.","solutions":["Remove the symlinked entries and copy/move the real data into the tree so every entry is a regular file or directory (bind mounts are also rejected at boundaries, so use physical copies)","Check whether an earlier relocation or disk-space workaround created the links; undo that layout","Verify no external tooling (backups, cleanup cron jobs) reintroduces symlinks, then re-run retirement"],"exampleFix":"// before: linked data file inside the legacy tree\n/srv/astrid/var/state/sstables/00000000000000000001.sst -> /mnt/big/00000000000000000001.sst\n// after: place a real copy in the tree\nrm /srv/astrid/var/state/sstables/00000000000000000001.sst\ncp /mnt/big/00000000000000000001.sst /srv/astrid/var/state/sstables/","handlingStrategy":"validation","validationCode":"fn tree_has_symlinks(root: &Path) -> std::io::Result<Vec<std::path::PathBuf>> {\n    let mut found = Vec::new();\n    for entry in std::fs::read_dir(root)? {\n        let child = entry?.path();\n        if std::fs::symlink_metadata(&child)?.file_type().is_symlink() {\n            found.push(child);\n        } else if child.is_dir() {\n            found.extend(tree_has_symlinks(&child)?);\n        }\n    }\n    Ok(found)\n}","typeGuard":"fn is_redirect(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|m| m.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"match retire_legacy_source_tree(&path) {\n    Err(e) if e.to_string().contains(\"contains a redirect\") => {\n        // parse offending child from the message, replace symlink with real data\n    }\n    other => other?,\n}","preventionTips":["Keep all legacy state data physically inside the tree; never symlink large files elsewhere","Warn users that relocating files via symlinks blocks migration/retirement","Run a symlink scan over the state tree before upgrading or retiring"],"tags":["rust","io","symlink","filesystem","security"],"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-14T05:17:10.506Z"}