astrid-runtime/astrid · error

durable capsule {id} has unsafe WIT metadata path {relative}

Error message

durable capsule {id} has unsafe WIT metadata path {relative}

What it means

verify_wit_files validates the WIT metadata of a durable capsule package read from principal storage. Every relative path in metadata.wit_files must consist only of Normal components — absolute paths, `..`, `.` or root components are rejected as unsafe before the file is looked up. This prevents a tampered metadata record from addressing files outside the capsule's wit/ namespace via traversal.

Source

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

    id: &str,
    metadata: &CapsuleMeta,
    files: &std::collections::BTreeMap<String, Vec<u8>>,
) -> anyhow::Result<()> {
    let mut expected = std::collections::BTreeSet::new();
    for (relative, pin) in &metadata.wit_files {
        let relative_path = Path::new(relative);
        if relative_path.is_absolute()
            || relative_path.components().any(|component| {
                matches!(
                    component,
                    std::path::Component::ParentDir | std::path::Component::RootDir
                )
            })
            || relative_path
                .components()
                .any(|component| !matches!(component, std::path::Component::Normal(_)))
        {
            bail!("durable capsule {id} has unsafe WIT metadata path {relative}");
        }
        let key = format!("wit/{relative}");
        let Some(bytes) = files.get(&key) else {
            bail!("durable capsule {id} is missing WIT file {relative}");
        };
        if !is_hex_digest(pin) || blake3::hash(bytes).to_hex().as_str() != pin {
            bail!("durable capsule {id} WIT digest mismatch for {relative}");
        }
        expected.insert(key);
    }
    for key in files.keys().filter(|key| key.starts_with("wit/")) {
        if Path::new(key)
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("wit"))
            && !expected.contains(key)
        {
            bail!("durable capsule {id} has an unpinned WIT file {key}");
        }

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the capsule's metadata (wit_files keys) and correct any absolute or `..` paths to plain relative names like `api.wit` or `deps/types.wit`.
  2. Reinstall or republish the capsule so metadata is regenerated by the tooling.
  3. If the store was hand-edited or restored, rebuild it from authoritative capsule sources.
  4. Report/fix the packager that emitted non-relative WIT paths.

Example fix

// before: unsafe metadata entry
wit_files: {"/abs/api.wit": "<pin>"}
// after: normal relative path
wit_files: {"api.wit": "<blake3-hex-pin>"}
Defensive patterns

Strategy: validation

Validate before calling

fn wit_paths_are_safe(wit_files: &std::collections::BTreeMap<String, String>) -> bool {
    wit_files.keys().all(|rel| {
        let p = std::path::Path::new(rel);
        !p.is_absolute()
            && p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
    })
}

Type guard

fn is_normal_relative(rel: &str) -> bool {
    let p = std::path::Path::new(rel);
    !p.is_absolute() && p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
}

Try / catch

match read_verified_durable_package_for_owner(id) {
    Err(e) if e.to_string().contains("unsafe WIT metadata path") => {
        // metadata record is corrupt/tampered: reinstall or republish the capsule
        reinstall_capsule(id)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling read_verified_durable_package_for_owner on a durable capsule whose CapsuleMeta.wit_files keys contain an absolute path or non-Normal components (`..`, `.`, root). Usually indicates a corrupted or hand-edited metadata record, or a bug in the code that wrote wit_files.

Common situations: Manually editing the durable store's metadata JSON; restoring a store from a partially migrated backup; a published capsule whose metadata was generated by an old/buggy packager embedding absolute WIT paths.

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