astrid-runtime/astrid · error

unsafe capsule archive path

Error message

unsafe capsule archive path '{}'

What it means

Fired by normalized_entry_path when a tar entry path in a capsule archive contains a non-Normal component (parent dir, root, or prefix), i.e. the path escapes or is absolute. Guarding against path-traversal entries in untrusted capsule archives.

Solutions

  1. Repack the capsule archive so all entry paths are plain relative, UTF-8 paths
  2. Inspect the archive with `tar -tf` and remove entries containing `..`, leading `/`, or drive prefixes
  3. Obtain a freshly built capsule from the publisher
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/astrid-build/src/artifact.rs:297 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at crates/astrid-build/src/artifact.rs:297

        }
        records.push(hash_reader(path, entry.size(), &mut entry)?);
    }
    Ok((records, envelope))
}

fn normalized_entry_path<R: Read>(entry: &tar::Entry<'_, R>) -> anyhow::Result<String> {
    let path = entry.path().context("invalid capsule archive path")?;
    let mut parts = Vec::new();
    for component in path.components() {
        match component {
            Component::Normal(part) => parts.push(
                part.to_str()
                    .context("capsule archive paths must be UTF-8")?
                    .to_string(),
            ),
            Component::CurDir => {},
            Component::ParentDir | Component::RootDir | Component::Prefix(_) => {
                bail!("unsafe capsule archive path '{}'", path.display());
            },
        }
    }
    if parts.is_empty() {
        bail!("capsule archive contains an empty path");
    }
    Ok(parts.join("/"))
}

fn hash_reader(path: String, size: u64, reader: &mut impl Read) -> anyhow::Result<ContentRecord> {
    let mut hasher = blake3::Hasher::new();
    let mut read = 0_u64;
    let mut buffer = [0_u8; 16 * 1024];
    loop {
        let count = reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }

View on GitHub (pinned to affd8760f4)