{"record":{"id":"c108951ff1414c7e","repo":"astrid-runtime/astrid","slug":"cache-changed-to-a-redirect-or-special-entry","errorCode":null,"errorMessage":"cache changed to a redirect or special entry: {}","messagePattern":"cache changed to a redirect or special entry: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/paths.rs","lineNumber":227,"sourceCode":"                \"cache contains a redirect or special entry: {}\",\n                path.display()\n            );\n        }\n        if metadata.is_dir() {\n            validate_cache_tree(&path)?;\n        } else {\n            astrid_core::platform_fs::verify_no_redirects(&path)\n                .with_context(|| format!(\"verify cache file {}\", path.display()))?;\n        }\n    }\n    Ok(())\n}\n\nfn remove_cache_tree(path: &Path) -> anyhow::Result<()> {\n    let metadata = std::fs::symlink_metadata(path)\n        .with_context(|| format!(\"inspect cache path {}\", path.display()))?;\n    if metadata.file_type().is_symlink() || (!metadata.is_dir() && !metadata.is_file()) {\n        anyhow::bail!(\n            \"cache changed to a redirect or special entry: {}\",\n            path.display()\n        );\n    }\n    if metadata.is_dir() {\n        for entry in std::fs::read_dir(path).with_context(|| format!(\"read {}\", path.display()))? {\n            remove_cache_tree(\n                &entry\n                    .with_context(|| format!(\"read cache entry under {}\", path.display()))?\n                    .path(),\n            )?;\n        }\n        std::fs::remove_dir(path).with_context(|| format!(\"remove {}\", path.display()))?;\n    } else {\n        std::fs::remove_file(path).with_context(|| format!(\"remove {}\", path.display()))?;\n    }\n    Ok(())\n}","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/paths.rs#L209-L245","documentation":"remove_cache_tree in astrid-capsule-install refuses to delete a materialization-cache path whose symlink_metadata shows it is a symlink or neither a directory nor a regular file (e.g. fifo, device, socket). The library treats symlinks and special files as 'redirects' that could point outside the cache, so instead of following/removing them blindly it bails to protect against path-redirect attacks or corrupted cache state. The word 'changed' indicates a TOCTOU check: the entry was expected to be a plain dir/file but mutated between validation and removal.","triggerScenarios":"Calling clear_capsule_materialization_cache (which recurses remove_cache_tree) while a cache entry is a symlink to elsewhere, or a special file (fifo/socket/device). Also fires if another process or attacker swaps a real cache entry for a symlink between the validate_cache_tree pass and the removal pass.","commonSituations":"Manual tinkering with the Astrid home cache directory (e.g. ln -s to move a large cache to another disk); a build tool creating fifos/sockets inside the cache; concurrent runs of the tool racing on the same cache tree; restored-from-backup caches containing symlinks.","solutions":["Inspect the reported path with `ls -la` and replace the symlink/special file with a real directory or file (or remove it yourself) before re-running.","Clear the offending entry manually (`rm` the symlink, `mkfifo`/socket leftovers) and let the tool rebuild the cache.","Stop processes that write into the cache concurrently so entries cannot be swapped mid-removal.","If symlinking the cache is intentional, it is unsupported: relocate the whole Astrid home instead of individual cache entries."],"exampleFix":"// before: symlinked cache entry pointing to another disk\nln -s /mnt/bigdisk/astrid-cache/entry ~/.astrid/cache/entry\nclear_capsule_materialization_cache(...) // -> cache changed to a redirect or special entry\n// after: remove the symlink and let the tool own the path\nrm ~/.astrid/cache/entry\nmkdir ~/.astrid/cache/entry","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(path)?;\nif md.file_type().is_symlink() || (!md.is_dir() && !md.is_file()) {\n    // resolve before invoking the cache-clearing API\n    eprintln!(\"cache entry {} is a redirect/special file; fix manually\", path.display());\n}","typeGuard":"fn is_plain_entry(md: &std::fs::Metadata) -> bool {\n    !md.file_type().is_symlink() && (md.is_dir() || md.is_file())\n}","tryCatchPattern":"match clear_capsule_materialization_cache() {\n    Err(e) if e.to_string().contains(\"redirect or special entry\") => {\n        // repair path manually or wipe the cache root, then retry once\n    }\n    other => other?,\n}","preventionTips":["Never symlink individual entries inside the Astrid cache; relocate the whole home instead","Do not let build tools create fifos/sockets inside the cache directory","Avoid concurrent processes mutating the same cache tree","Restore caches as real files/dirs, not symlinks"],"tags":["filesystem","symlink","cache","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"}