{"record":{"id":"fe759605cf5962ce","repo":"astrid-runtime/astrid","slug":"local-capsule-source-source-escapes-the-authen","errorCode":null,"errorMessage":"local capsule source {source:?} escapes the authenticated Distro.toml directory","messagePattern":"local capsule source (.+?) escapes the authenticated Distro\\.toml directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/local_source.rs","lineNumber":68,"sourceCode":"            \"local capsule source {source:?} requires a local authenticated Distro.toml; \\\n             remote manifests cannot resolve relative members\"\n        );\n    };\n\n    let root = manifest_path\n        .parent()\n        .ok_or_else(|| anyhow::anyhow!(\"Distro.toml has no parent directory\"))?;\n    let source_path = Path::new(source);\n    let candidate = if source_path.is_absolute() {\n        source_path.to_path_buf()\n    } else {\n        root.join(source_path)\n    };\n    if source_path\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\"local capsule source {source:?} escapes the authenticated Distro.toml directory\");\n    }\n\n    let canonical_root = root\n        .canonicalize()\n        .with_context(|| format!(\"failed to resolve Distro.toml directory {}\", root.display()))?;\n    let canonical_path = candidate\n        .canonicalize()\n        .with_context(|| format!(\"failed to resolve local capsule source {source:?}\"))?;\n    if !canonical_path.starts_with(&canonical_root) {\n        bail!(\"local capsule source {source:?} escapes the authenticated Distro.toml directory\");\n    }\n    let metadata = std::fs::metadata(&canonical_path)\n        .with_context(|| format!(\"failed to stat local capsule source {source:?}\"))?;\n    if !metadata.is_file() {\n        bail!(\"local capsule source {source:?} is not a regular file\");\n    }\n    if metadata.len() > MAX_MEMBER_BYTES {\n        bail!(","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/local_source.rs#L50-L86","documentation":"As a cheap pre-canonicalization check, resolve_local_capsule_archive rejects any source path containing a `..` (ParentDir) component. Such a path could climb out of the authenticated Distro.toml directory, so it is treated as an escape attempt and bails before any filesystem access.","triggerScenarios":"A Distro.toml capsule source like `../shared/capsule.capsule` or `a/../../escape.capsule`; source_path contains Component::ParentDir.","commonSituations":"Hand-written manifests referencing shared capsules in a sibling directory via `..`; copy-pasting relative paths from another project layout.","solutions":["Rewrite the source as a path relative to the Distro.toml directory without `..` segments","Move or symlink the capsule into the manifest's directory tree and reference it directly","Publish the shared capsule to the registry and reference it remotely instead"],"exampleFix":"// before\nsource = \"../shared/capsule.capsule\"\n// after\nsource = \"vendor/shared/capsule.capsule\"","handlingStrategy":"validation","validationCode":"let p = std::path::Path::new(source);\nif p.components().any(|c| c == std::path::Component::ParentDir) {\n    anyhow::bail!(\"source must stay inside the Distro.toml directory\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Author capsule sources as clean relative paths under the manifest directory","Never use `..` segments in Distro.toml source fields","Lint manifests for ParentDir components in CI"],"tags":["path-traversal","security","manifest"],"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"}