{"record":{"id":"46ebc24f2b72f0fe","repo":"astrid-runtime/astrid","slug":"cache-contains-a-redirect-or-special-entry","errorCode":null,"errorMessage":"cache contains a redirect or special entry: {}","messagePattern":"cache contains a redirect or special entry: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/paths.rs","lineNumber":208,"sourceCode":"        let path = entry\n            .context(\"read capsule materialization cache entry\")?\n            .path();\n        remove_cache_tree(&path)?;\n    }\n    Ok(())\n}\n\nfn validate_cache_tree(path: &Path) -> anyhow::Result<()> {\n    astrid_core::platform_fs::verify_no_redirects(path)\n        .with_context(|| format!(\"verify cache path {}\", path.display()))?;\n    for entry in std::fs::read_dir(path).with_context(|| format!(\"read {}\", path.display()))? {\n        let path = entry\n            .with_context(|| format!(\"read cache entry under {}\", path.display()))?\n            .path();\n        let metadata = std::fs::symlink_metadata(&path)\n            .with_context(|| format!(\"inspect {}\", path.display()))?;\n        if metadata.file_type().is_symlink() || (!metadata.is_dir() && !metadata.is_file()) {\n            anyhow::bail!(\n                \"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()) {","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/paths.rs#L190-L226","documentation":"validate_cache_tree recursively walks the materialization cache and rejects any entry that is a symlink or neither a directory nor a regular file (fifos, sockets, devices). This enforces that the cache tree contains only plain materialized content, protecting against redirect attacks and special files inside a tree that will later be deleted or executed from. It recurses into subdirectories and is also called by itself.","triggerScenarios":"A symlink planted inside the cache (e.g. pointing to /etc) or a socket/fifo/device node appearing under the cache root when clear_capsule_materialization_cache calls validate_cache_tree, or when validate_cache_tree is invoked recursively.","commonSituations":"Malicious or curious tooling creating links inside the shared cache directory; build processes that leave sockets/fifos in cache paths; restored-from-backup caches containing symlinks; crash residue from another program.","solutions":["Inspect the reported path (`ls -la`) and delete the offending symlink/special entry, then re-run the cleanup","Purge and rebuild the whole cache directory after verifying the root itself is safe","Find the process that created the non-regular entry and stop it from writing into the cache","Restrict write access to the cache directory to the runtime user only"],"exampleFix":"// before\n$ find ~/.cache/astrid/capsules -type l   # shows link -> /etc\n// after\n$ rm ~/.cache/astrid/capsules/<offending-link>\n# or full reset:\n$ rm -rf ~/.cache/astrid/capsules && mkdir ~/.cache/astrid/capsules","handlingStrategy":"validation","validationCode":"fn cache_tree_has_only_regular_entries(root: &Path) -> anyhow::Result<bool> {\n    for entry in std::fs::read_dir(root)? {\n        let p = entry?.path();\n        let m = std::fs::symlink_metadata(&p)?;\n        if m.file_type().is_symlink() || (!m.is_dir() && !m.is_file()) {\n            return Ok(false);\n        }\n        if m.is_dir() && !cache_tree_has_only_regular_entries(&p)? { return Ok(false); }\n    }\n    Ok(true)\n}","typeGuard":"fn is_plain_entry(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && (m.is_dir() || m.is_file())).unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = clear_capsule_materialization_cache(&root) {\n    if e.to_string().contains(\"redirect or special entry\") {\n        // extract the offending path, delete it, retry\n    } else { return Err(e); }\n}","preventionTips":["Restrict cache directory write access to the runtime user only","Scan for symlinks/special files in shared cache dirs on startup","Ensure build tooling never leaves sockets or fifos under the cache","After restoring caches from backups, re-verify the tree before cleanup"],"tags":["cache","symlink","security","path-traversal"],"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"}