astrid-runtime/astrid · error
capsule cache path contains unsafe components
Error message
capsule cache path contains unsafe components
What it means
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.
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
Example fix
// before (hand-built path)
let dir = cache_root.join(format!("{}/../{}/{}", owner, id, digest));
// after
let dir = published_cache_target(principal, manifest, snapshot)?; Defensive patterns
Strategy: validation
Validate before calling
fn all_normal_components(p: &Path) -> bool {
p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
}
if !all_normal_components(relative) { bail!("unsafe cache path components"); } Type guard
fn is_safe_cache_relative(p: &Path) -> bool {
p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- capsule cache path is outside the durable registry cache
- capsule cache path is redirected
- cache changed to a redirect or special entry
- cache contains a redirect or special entry
- capsule cache owner or id does not match authenticated…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/6a57ee1d1d6d4bc5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/lib.rs:1591
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn validate_published_cache_path(
&self,
dir: &Path,
principal: &PrincipalId,
manifest: &astrid_capsule_types::manifest::CapsuleManifest,
snapshot: &astrid_storage::CapsulePackageSnapshot,
) -> anyhow::Result<()> {
let cache_root = self.astrid_home.run_dir().join("capsules");
let relative = dir.strip_prefix(&cache_root).map_err(|_| {
anyhow::anyhow!("capsule cache path is outside the durable registry cache")
})?;
astrid_core::platform_fs::verify_no_redirects(dir)
.map_err(|error| anyhow::anyhow!("capsule cache path is redirected: {error}"))?;
let components: Vec<String> = relative
.components()
.map(|component| match component {
std::path::Component::Normal(value) => Ok(value.to_string_lossy().into_owned()),
_ => Err(anyhow::anyhow!(
"capsule cache path contains unsafe components"
)),
})
.collect::<anyhow::Result<_>>()?;
if components.len() != 3 {
anyhow::bail!("capsule cache path does not contain owner/id/digest components");
}
let uid = self
.principal_directory
.uid_for(principal)
.map_err(|error| anyhow::anyhow!("resolve capsule cache owner UID: {error}"))?;
if components[0] != uid.to_string() || components[1] != manifest.package.name {
anyhow::bail!("capsule cache owner or id does not match authenticated registry scope");
}
let digest = blake3::hash(&snapshot.package().archive)
.to_hex()
.to_string();
if components[2] != digest {View on GitHub (pinned to affd8760f4)