astrid-runtime/astrid · error

capsule projection escaped its root: {}

Error message

capsule projection escaped its root: {}

What it means

Thrown when `path.strip_prefix(root)` fails while walking a capsule projection, meaning an entry's path is not under the projection root. This is a safety check against path traversal — a projection entry escaping its declared root is treated as corruption/attack and aborts the walk.

Source

Thrown at crates/astrid-kernel/src/lib.rs:1630

        Ok(())
    }

    /// Inventory a projection without traversing redirects or special files.
    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    fn inventory_projection_files(root: &Path) -> anyhow::Result<ProjectionInventory> {
        fn walk(
            root: &Path,
            directory: &Path,
            inventory: &mut ProjectionInventory,
        ) -> anyhow::Result<()> {
            for entry in std::fs::read_dir(directory).map_err(|error| {
                anyhow::anyhow!("read capsule projection {}: {error}", directory.display())
            })? {
                let entry = entry
                    .map_err(|error| anyhow::anyhow!("read capsule projection entry: {error}"))?;
                let path = entry.path();
                let relative = path.strip_prefix(root).map_err(|_| {
                    anyhow::anyhow!("capsule projection escaped its root: {}", path.display())
                })?;
                let relative_text = relative.to_str().ok_or_else(|| {
                    anyhow::anyhow!("capsule projection path is not UTF-8: {}", path.display())
                })?;
                let metadata = std::fs::symlink_metadata(&path).map_err(|error| {
                    anyhow::anyhow!("inspect capsule projection {}: {error}", path.display())
                })?;
                let file_type = metadata.file_type();
                if file_type.is_symlink() {
                    anyhow::bail!(
                        "capsule projection contains a symbolic link: {}",
                        path.display()
                    );
                }
                if file_type.is_dir() {
                    inventory.directories.insert(relative_text.to_owned());
                    walk(root, &path, inventory)?;
                } else if file_type.is_file() {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Do not pass mismatched root/directory arguments — directory must equal root or be beneath it.
  2. Rebuild/re-extract the capsule projection; treat the existing projection as corrupted.
  3. Verify the capsule source is trusted and free of out-of-root symlinks before projection.
  4. If intentional, project only content confined to the root.

Example fix

// before
walk(&subdir_root, &root, &mut inv)?; // mismatched
// after
walk(&root, &root, &mut inv)?;
Defensive patterns

Strategy: validation

Validate before calling

// reject out-of-root links before projecting
for entry in walk_source(capsule_dir) {
    let meta = std::fs::symlink_metadata(&entry)?;
    if meta.file_type().is_symlink() {
        anyhow::bail!("refusing to project symlink: {}", entry.display());
    }
}

Type guard

fn stays_under_root(root: &Path, p: &Path) -> bool { p.strip_prefix(root).is_ok() }

Try / catch

if let Err(e) = load_projection(root) {
    if e.to_string().contains("escaped its root") {
        // treat projection as corrupted: rebuild from a trusted capsule source
    }
}

Prevention

When it happens

Trigger: A capsule projection directory contains a symlink or hardlink whose resolved path lies outside the root, or the walk is invoked with mismatched root/directory arguments.

Common situations: Malicious or corrupted capsule content planting symlinks pointing outside the projection; calling walk with a subdirectory as `root` but a sibling path; inconsistent root after a rename/move.

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/e22441fae7cbe8d4. Report an issue: GitHub.