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
- 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`.
- Reinstall or republish the capsule so metadata is regenerated by the tooling.
- If the store was hand-edited or restored, rebuild it from authoritative capsule sources.
- 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
- Never hand-edit durable store metadata files
- Keep WIT paths as plain relative names (no leading /, no ..)
- Rebuild capsules from source after any restore/migration instead of patching metadata
- Validate wit_files keys when generating metadata programmatically
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
- private directory contains traversal: {}
- workspace descendant must be a non-empty relative path witho
- durable capsule {} has malformed contracts pin
- refusing to seed canonical from a non-content-address contra
- symlink {} resolves outside the capsule source root ({}); re
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/98e97b7769b8ec0b.
Report an issue: GitHub.