astrid-runtime/astrid · error
capsule path is not canonical: {path}
Error message
capsule path is not canonical: {path} What it means
Raised by append_entry while writing an entry into the canonical tar archive: the relative path is absolute, contains a `..` segment, or has an empty segment. Canonical capsules must contain only normalized, relative, root-anchored paths, so the entry is rejected rather than written.
Source
Thrown at crates/astrid-capsule-install/src/storage.rs:863
let metadata = fs::symlink_metadata(&name)
.with_context(|| format!("inspect capsule source entry {}", name.display()))?;
children.push((name, metadata));
}
children.sort_by(|left, right| left.0.cmp(&right.0));
Ok(children)
}
fn append_entry(
builder: &mut Builder<GzEncoder<Vec<u8>>>,
root: &Path,
relative: &Path,
metadata: &Metadata,
) -> anyhow::Result<()> {
let path = relative
.to_str()
.ok_or_else(|| anyhow::anyhow!("capsule path is not valid UTF-8"))?;
if path.starts_with('/') || path.split('/').any(|part| part == ".." || part.is_empty()) {
bail!("capsule path is not canonical: {path}");
}
let mut header = Header::new_gnu();
header.set_uid(0);
header.set_gid(0);
header.set_mtime(0);
header.set_mode(if metadata.is_dir() { 0o755 } else { 0o644 });
if metadata.is_dir() {
header.set_entry_type(EntryType::Directory);
header.set_size(0);
header.set_cksum();
builder
.append_data(&mut header, path, io::empty())
.with_context(|| format!("append capsule directory {path}"))?;
} else {
let mut file =
File::open(root.join(relative)).with_context(|| format!("open capsule file {path}"))?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)View on GitHub (pinned to affd8760f4)
Solutions
- Normalize the offending path: make it relative to the capsule root with no `..`, `.`, empty, or duplicate slash segments.
- Fix any in-tree symlink whose stored relative target contains `..`; use paths anchored at the root instead.
- If you call collect_entries/canonical_capsule_archive with custom paths, derive them via path joins from the root rather than string concatenation.
Example fix
// before: relative path with a parent segment let relative = "assets/../lib/main.wit"; // after: normalized canonical path let relative = "lib/main.wit";
Defensive patterns
Strategy: validation
Validate before calling
fn path_is_canonical(p: &str) -> bool {
!p.starts_with('/') && p.split('/').all(|part| !part.is_empty() && part != "..")
} Type guard
fn is_canonical_relative(p: &Path) -> bool {
p.is_relative() && p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
} Try / catch
if !path_is_canonical(&rel) {
eprintln!("normalize archive paths before publishing: {rel}");
std::process::exit(1);
} Prevention
- Build relative paths via PathBuf::join from the root, never string concat
- Avoid symlink targets containing `..`
- Normalize paths with path.components() before adding to archives
When it happens
Trigger: canonical_capsule_archive encountering a collected relative path that is not canonical — typically from a symlink relative target with `..` components, or unusual file names with `//` produced by joining; entries reaching append_entry fail the starts_with('/') / `..` / empty-segment check.
Common situations: Symlink targets written as `../foo` inside the tree; files created with odd names containing double slashes via tooling; custom code paths that hand-build relative paths instead of deriving them from the root walk.
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
- legacy capsule WIT path is not relative: {}
- cannot remove capsule authority while an install transaction
- an incomplete capsule authority update exists at {}; remove
- unsupported installed authority schema {}
- installed capsule identity/version differs from its authorit
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/0e5c8d7c85abd946.
Report an issue: GitHub.