astrid-runtime/astrid · error

local capsule source

Error message

local capsule source {source:?} escapes the authenticated Distro.toml directory

What it means

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.

Solutions

  1. Rewrite the source as a path relative to the Distro.toml directory without `..` segments
  2. Move or symlink the capsule into the manifest's directory tree and reference it directly
  3. Publish the shared capsule to the registry and reference it remotely instead

Example fix

// before
source = "../shared/capsule.capsule"
// after
source = "vendor/shared/capsule.capsule"
Defensive patterns

Strategy: validation

Validate before calling

let p = std::path::Path::new(source);
if p.components().any(|c| c == std::path::Component::ParentDir) {
    anyhow::bail!("source must stay inside the Distro.toml directory");
}

Prevention

When it happens

Trigger: A Distro.toml capsule source like `../shared/capsule.capsule` or `a/../../escape.capsule`; source_path contains Component::ParentDir.

Common situations: Hand-written manifests referencing shared capsules in a sibling directory via `..`; copy-pasting relative paths from another project layout.

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/fe759605cf5962ce. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/distro/local_source.rs:68

            "local capsule source {source:?} requires a local authenticated Distro.toml; \
             remote manifests cannot resolve relative members"
        );
    };

    let root = manifest_path
        .parent()
        .ok_or_else(|| anyhow::anyhow!("Distro.toml has no parent directory"))?;
    let source_path = Path::new(source);
    let candidate = if source_path.is_absolute() {
        source_path.to_path_buf()
    } else {
        root.join(source_path)
    };
    if source_path
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!("local capsule source {source:?} escapes the authenticated Distro.toml directory");
    }

    let canonical_root = root
        .canonicalize()
        .with_context(|| format!("failed to resolve Distro.toml directory {}", root.display()))?;
    let canonical_path = candidate
        .canonicalize()
        .with_context(|| format!("failed to resolve local capsule source {source:?}"))?;
    if !canonical_path.starts_with(&canonical_root) {
        bail!("local capsule source {source:?} escapes the authenticated Distro.toml directory");
    }
    let metadata = std::fs::metadata(&canonical_path)
        .with_context(|| format!("failed to stat local capsule source {source:?}"))?;
    if !metadata.is_file() {
        bail!("local capsule source {source:?} is not a regular file");
    }
    if metadata.len() > MAX_MEMBER_BYTES {
        bail!(

View on GitHub (pinned to affd8760f4)