astrid-runtime/astrid · error

unsafe archive path '{path}'

Error message

unsafe archive path '{path}'

What it means

`validate_archive_path` rejects archive paths that are absolute or contain `..` components, preventing capsules from being written to arbitrary filesystem locations. Only relative, traversal-free paths are accepted.

Source

Thrown at crates/astrid-cli/src/commands/distro/shuttle.rs:258

/// The archive-relative path of a capsule member: `capsules/<name>.capsule`.
pub(crate) fn capsule_member_path(name: &str) -> String {
    format!("{CAPSULES_DIR}/{name}.capsule")
}

/// The on-disk path of a capsule inside an unpacked mirror.
pub(crate) fn capsule_mirror_path(mirror: &Path, name: &str) -> PathBuf {
    mirror.join(CAPSULES_DIR).join(format!("{name}.capsule"))
}

/// Reject archive paths that are absolute, contain `..`, or are empty.
fn validate_archive_path(path: &str) -> anyhow::Result<()> {
    if path.is_empty() {
        bail!("empty archive path");
    }
    let p = Path::new(path);
    if p.is_absolute() || p.components().any(|c| matches!(c, Component::ParentDir)) {
        bail!("unsafe archive path '{path}'");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_entries() -> Vec<ShuttleEntry> {
        vec![
            ShuttleEntry {
                path: SIG_NAME.to_string(),
                content: ShuttleContent::Bytes(b"deadbeef".to_vec()),
            },
            ShuttleEntry {
                path: MANIFEST_NAME.to_string(),
                content: ShuttleContent::Bytes(b"schema-version = 1\n".to_vec()),
            },

View on GitHub (pinned to affd8760f4)

Solutions

  1. Use a relative path without `..`, e.g. `--out my.capsule` or `--out out/my.capsule`.
  2. `cd` to the intended output directory and pass a bare filename.
  3. Omit the path so pack writes to the default mirror location `<name>.capsule`.

Example fix

// before
$ astrid distro pack --out /tmp/x.capsule
// after
$ cd /tmp && astrid distro pack --out x.capsule
Defensive patterns

Strategy: validation

Validate before calling

fn archive_path_is_safe(p: &str) -> bool {
    let path = std::path::Path::new(p);
    !path.is_absolute()
        && !path.components().any(|c| matches!(c, std::path::Component::ParentDir))
}

Type guard

fn is_safe_out_path(p: &str) -> bool {
    let q = Path::new(p);
    q.is_relative() && !q.components().any(|c| matches!(c, Component::ParentDir))
}

Try / catch

match pack(&staging, out) {
    Err(e) if e.to_string().contains("unsafe archive path") => {
        eprintln!("use a relative path without '..': {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `pack` with an output path like `/tmp/foo.capsule` or `../foo.capsule`; `validate_archive_path` bails before the archive is created.

Common situations: Users passing an absolute path out of habit (`--out $(pwd)/x.capsule`); scripts built on a different machine using `..`-relative paths; path built by string concatenation from user input.

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