astrid-runtime/astrid · error

capsule archive contains an unsafe path {}

Error message

capsule archive contains an unsafe path {}

What it means

While unpacking a user-supplied capsule archive into a staging directory to compute its canonical digest, every entry path is checked for safety: absolute paths or any ParentDir (`..`) component are rejected. This is a path-traversal guard — a malicious or malformed archive could otherwise escape the staging directory and overwrite files outside it during digesting.

Source

Thrown at crates/astrid-capsule-install/src/source_digest.rs:47

            source.display()
        );
    }

    let staging = tempfile::tempdir().context("create source digest staging directory")?;
    let file = fs::File::open(source)
        .with_context(|| format!("open capsule archive {}", source.display()))?;
    let decoder = flate2::read::GzDecoder::new(file);
    let mut archive = tar::Archive::new(decoder);
    let mut names = BTreeSet::new();
    for entry in archive.entries().context("read capsule archive entries")? {
        let mut entry = entry.context("read capsule archive entry")?;
        let path = entry.path().context("read capsule archive path")?;
        if path.is_absolute()
            || path
                .components()
                .any(|component| matches!(component, std::path::Component::ParentDir))
        {
            bail!("capsule archive contains an unsafe path {}", path.display());
        }
        let name = path
            .to_str()
            .ok_or_else(|| anyhow::anyhow!("capsule archive path is not UTF-8"))?
            .replace('\\', "/");
        if !names.insert(name.clone()) {
            bail!("capsule archive contains duplicate path {name}");
        }
        let entry_type = entry.header().entry_type();
        if !entry_type.is_dir() && !entry_type.is_file() {
            bail!("capsule archive contains a link or special file {name}");
        }
        let destination = staging.path().join(&path);
        if entry_type.is_dir() {
            fs::create_dir_all(&destination)
                .with_context(|| format!("create capsule archive directory {name}"))?;
            continue;
        }

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the archive with `tar -tzvf capsule.tgz` and find the offending entry.
  2. Rebuild the archive with relative paths from the capsule root: `tar -czf capsule.tgz -C <capsule-root> .`.
  3. Strip leading prefixes/`..` segments when repacking (e.g. with `tar --transform` or a build script).
  4. Only digest archives from trusted sources; reject ones you did not build yourself.

Example fix

// before: archive built from wrong cwd
// tar -czf capsule.tgz ../my-capsule/Capsule.toml  (entries contain ..)
// after: build from inside the capsule root
// cd my-capsule && tar -czf ../capsule.tgz .
Defensive patterns

Strategy: validation

Validate before calling

// inspect the archive before digesting
let out = std::process::Command::new("tar")
    .args(["-tzf", archive_path])
    .output()?;
let unsafe_entry = String::from_utf8_lossy(&out.stdout)
    .lines()
    .any(|l| l.starts_with('/') || l.split('/').any(|seg| seg == ".."));
if unsafe_entry { eprintln!("archive has absolute or .. paths; rebuild it"); }

Type guard

fn archive_paths_are_safe(names: &[String]) -> bool {
    names.iter().all(|n| !n.starts_with('/') && !n.split('/').any(|s| s == ".."))
}

Try / catch

match archive_digest_for_source(archive) {
    Err(e) if e.to_string().contains("unsafe path") => {
        anyhow::bail!("rebuild the archive with relative paths: tar -czf capsule.tgz -C <root> .");
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling archive_digest_for_source on a .tar.gz whose entries include absolute paths (e.g. `/etc/passwd`) or `..` components (e.g. `../../evil`), typically produced by packaging tools that used absolute source paths or bad base-directory handling.

Common situations: Hand-built tar archives with `tar -C` mistakes; archives created on Windows with drive-letter absolute paths; a malicious or tampered capsule archive; archives regenerated after directory restructuring without stripping a common prefix.

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