{"record":{"id":"678cb6e6dea6465f","repo":"astrid-runtime/astrid","slug":"capsule-cache-path-is-redirected-error","errorCode":null,"errorMessage":"capsule cache path is redirected: {error}","messagePattern":"capsule cache path is redirected: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":1586,"sourceCode":"        )\n        .map_err(|error| anyhow::anyhow!(\"resolve durable capsule cache target: {error}\"))\n    }\n\n    /// Validate the disposable cache path against an exact owner snapshot.\n    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]\n    fn validate_published_cache_path(\n        &self,\n        dir: &Path,\n        principal: &PrincipalId,\n        manifest: &astrid_capsule_types::manifest::CapsuleManifest,\n        snapshot: &astrid_storage::CapsulePackageSnapshot,\n    ) -> anyhow::Result<()> {\n        let cache_root = self.astrid_home.run_dir().join(\"capsules\");\n        let relative = dir.strip_prefix(&cache_root).map_err(|_| {\n            anyhow::anyhow!(\"capsule cache path is outside the durable registry cache\")\n        })?;\n        astrid_core::platform_fs::verify_no_redirects(dir)\n            .map_err(|error| anyhow::anyhow!(\"capsule cache path is redirected: {error}\"))?;\n        let components: Vec<String> = relative\n            .components()\n            .map(|component| match component {\n                std::path::Component::Normal(value) => Ok(value.to_string_lossy().into_owned()),\n                _ => Err(anyhow::anyhow!(\n                    \"capsule cache path contains unsafe components\"\n                )),\n            })\n            .collect::<anyhow::Result<_>>()?;\n        if components.len() != 3 {\n            anyhow::bail!(\"capsule cache path does not contain owner/id/digest components\");\n        }\n        let uid = self\n            .principal_directory\n            .uid_for(principal)\n            .map_err(|error| anyhow::anyhow!(\"resolve capsule cache owner UID: {error}\"))?;\n        if components[0] != uid.to_string() || components[1] != manifest.package.name {\n            anyhow::bail!(\"capsule cache owner or id does not match authenticated registry scope\");","sourceCodeStart":1568,"sourceCodeEnd":1604,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L1568-L1604","documentation":"Wraps `astrid_core::platform_fs::verify_no_redirects(dir)` failing: although the cache dir is under the registry cache root, it contains a symlink or redirect component that could escape the durable cache. The kernel refuses to trust the path.","triggerScenarios":"validate_published_cache_path finds a symlink anywhere inside the computed capsule cache directory tree (owner, id, or digest level) when validating an exact owner snapshot.","commonSituations":"Admin symlinked a capsule cache dir to shared storage or another disk; archive extraction created symlinks; restore/backup tooling materialized links; attacker-supplied archive with link entries.","solutions":["Replace the symlinked component inside run_dir/capsules with a real directory","Purge the affected capsule cache directory and re-install/re-publish to repopulate it","Extract capsule archives without preserving symlinks","Audit how the cache was populated (backup restore, rsync -a, manual ln -s)"],"exampleFix":"// before\n~/.astrid/run/capsules/1001/my-capsule/<digest> -> /mnt/shared/cache\n// after\nrm ~/.astrid/run/capsules/1001/my-capsule/<digest>\nmkdir -p ~/.astrid/run/capsules/1001/my-capsule/<digest> && # reinstall real files","handlingStrategy":"validation","validationCode":"astrid_core::platform_fs::verify_no_redirects(dir)?; // pre-check before install","typeGuard":"fn has_no_symlinks(dir: &Path) -> bool {\n    dir.symlink_metadata().map(|m| !m.file_type().is_symlink()).unwrap_or(false)\n        && std::fs::read_dir(dir).map(|rd| rd.filter_map(Result::ok).all(|e| has_no_symlinks(&e.path()))).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Extract archives with --no-same-owner and no symlink preservation","Avoid symlinked shared storage for run_dir/capsules","Audit restore/backup pipelines for symlink materialization"],"tags":["symlink","path-safety","security","cache"],"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"}