{"record":{"id":"6a57ee1d1d6d4bc5","repo":"astrid-runtime/astrid","slug":"capsule-cache-path-contains-unsafe-components","errorCode":null,"errorMessage":"capsule cache path contains unsafe components","messagePattern":"capsule cache path contains unsafe components","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":1591,"sourceCode":"    #[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\");\n        }\n        let digest = blake3::hash(&snapshot.package().archive)\n            .to_hex()\n            .to_string();\n        if components[2] != digest {","sourceCodeStart":1573,"sourceCodeEnd":1609,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L1573-L1609","documentation":"Static error emitted when a component of the cache-relative path is not a plain Normal component (e.g. ParentDir, CurDir, RootDir, Prefix). After stripping the cache root, every remaining component must be a normal name because they become owner/id/digest path segments.","triggerScenarios":"validate_published_cache_path's components().map(...) hits a non-Normal Component in the path relative to the cache root — typically '..' or '.' segments smuggled into owner/id/digest strings.","commonSituations":"Capsule id or digest fields in the registry snapshot contain '../' or '.' segments from a tampered or hand-edited store; path constructed with string concatenation instead of the resolver; case-sensitivity/mismatch producing odd relative paths.","solutions":["Inspect the registry snapshot owner/id/digest values and remove any '..' or '.' segments","Regenerate the cache target with published_cache_target instead of hand-building paths","Re-publish the capsule so the registry stores sanitized owner/id/digest values","Delete the tampered registry entry and reinstall from a trusted source"],"exampleFix":"// before (hand-built path)\nlet dir = cache_root.join(format!(\"{}/../{}/{}\", owner, id, digest));\n// after\nlet dir = published_cache_target(principal, manifest, snapshot)?;","handlingStrategy":"validation","validationCode":"fn all_normal_components(p: &Path) -> bool {\n    p.components().all(|c| matches!(c, std::path::Component::Normal(_)))\n}\nif !all_normal_components(relative) { bail!(\"unsafe cache path components\"); }","typeGuard":"fn is_safe_cache_relative(p: &Path) -> bool {\n    p.components().all(|c| matches!(c, std::path::Component::Normal(_)))\n}","tryCatchPattern":null,"preventionTips":["Never build cache paths by string concatenation; use the resolver","Sanitize owner/id/digest fields before they enter the registry","Treat registry snapshots from untrusted sources as hostile input"],"tags":["path-safety","validation","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-17T15:17:12.973Z"}