astrid-runtime/astrid · error

capsule source symlink {} resolves outside source root

Error message

capsule source symlink {} resolves outside source root

What it means

Raised by collect_entries during canonical_capsule_archive when a symlink inside the capsule source canonicalizes to a path outside the source root. The library permits file symlinks (to keep npm-style node_modules/.bin working) only if their targets stay within the capsule, preventing archive content that escapes the source tree.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:789

    root: &Path,
    current: &Path,
    entries: &mut Vec<(PathBuf, Metadata)>,
) -> anyhow::Result<()> {
    let mut children = read_dir_sorted(current)?;
    for (name, metadata) in children.drain(..) {
        let relative = name
            .strip_prefix(root)
            .map_err(|_| anyhow::anyhow!("capsule path escaped source root"))?
            .to_path_buf();
        let file_type = metadata.file_type();
        if file_type.is_symlink() {
            let resolved = fs::canonicalize(&name).with_context(|| {
                format!("canonicalize capsule source symlink {}", relative.display())
            })?;
            let canonical_root = fs::canonicalize(root)
                .with_context(|| format!("canonicalize capsule source root {}", root.display()))?;
            if !resolved.starts_with(&canonical_root) {
                bail!(
                    "capsule source symlink {} resolves outside source root",
                    relative.display()
                );
            }
            let resolved_metadata = fs::metadata(&resolved).with_context(|| {
                format!("stat capsule source symlink target {}", relative.display())
            })?;
            if !resolved_metadata.is_file() {
                bail!(
                    "capsule source symlink {} does not resolve to a regular file",
                    relative.display()
                );
            }
            // File links are materialized as regular archive entries. This
            // preserves npm's node_modules/.bin links without ever storing a
            // redirect in the durable package.
            entries.push((relative, resolved_metadata));
            continue;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Move the symlink target inside the capsule source directory and update the link to a relative in-tree path.
  2. Replace the symlink with a real copy of the target file inside the capsule.
  3. If the content belongs to another package, publish it separately and depend on it rather than linking.

Example fix

// before: escapes the source root
vendor/lib.wit -> ../../shared/lib.wit

// after: keep the target inside the capsule
vendor/lib.wit (regular file copy of shared/lib.wit)
Defensive patterns

Strategy: validation

Validate before calling

fn symlink_escapes_root(link: &Path, root: &Path) -> std::io::Result<bool> {
    let target = std::fs::canonicalize(link)?;
    let root = std::fs::canonicalize(root)?;
    Ok(!target.starts_with(root))
}

Type guard

fn is_in_tree_link(link: &Path, root: &Path) -> bool {
    std::fs::canonicalize(link).ok()
        .zip(std::fs::canonicalize(root).ok())
        .map_or(false, |(t, r)| t.starts_with(r))
}

Try / catch

match publish(...) {
    Err(e) if e.to_string().contains("resolves outside source root") => { eprintln!("copy linked assets into the capsule"); exit(1); }
    other => other?,
}

Prevention

When it happens

Trigger: Archiving a capsule whose directory contains a symlink pointing to a file outside the capsule directory (e.g. ../shared/lib.wit or /usr/share/foo); fs::canonicalize resolves it and the starts_with(canonical_root) check fails.

Common situations: Sharing a common asset via ../ links during development; node_modules links hoisted above the package root in monorepos; linking to a globally installed tool or config file.

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