astrid-runtime/astrid · error
capsule archive contains an unsafe path {}
Error message
capsule archive contains an unsafe path {} What it means
While unpacking a user-supplied capsule archive into a staging directory to compute its canonical digest, every entry path is checked for safety: absolute paths or any ParentDir (`..`) component are rejected. This is a path-traversal guard — a malicious or malformed archive could otherwise escape the staging directory and overwrite files outside it during digesting.
Source
Thrown at crates/astrid-capsule-install/src/source_digest.rs:47
source.display()
);
}
let staging = tempfile::tempdir().context("create source digest staging directory")?;
let file = fs::File::open(source)
.with_context(|| format!("open capsule archive {}", source.display()))?;
let decoder = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(decoder);
let mut names = BTreeSet::new();
for entry in archive.entries().context("read capsule archive entries")? {
let mut entry = entry.context("read capsule archive entry")?;
let path = entry.path().context("read capsule archive path")?;
if path.is_absolute()
|| path
.components()
.any(|component| matches!(component, std::path::Component::ParentDir))
{
bail!("capsule archive contains an unsafe path {}", path.display());
}
let name = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("capsule archive path is not UTF-8"))?
.replace('\\', "/");
if !names.insert(name.clone()) {
bail!("capsule archive contains duplicate path {name}");
}
let entry_type = entry.header().entry_type();
if !entry_type.is_dir() && !entry_type.is_file() {
bail!("capsule archive contains a link or special file {name}");
}
let destination = staging.path().join(&path);
if entry_type.is_dir() {
fs::create_dir_all(&destination)
.with_context(|| format!("create capsule archive directory {name}"))?;
continue;
}View on GitHub (pinned to affd8760f4)
Solutions
- Inspect the archive with `tar -tzvf capsule.tgz` and find the offending entry.
- Rebuild the archive with relative paths from the capsule root: `tar -czf capsule.tgz -C <capsule-root> .`.
- Strip leading prefixes/`..` segments when repacking (e.g. with `tar --transform` or a build script).
- Only digest archives from trusted sources; reject ones you did not build yourself.
Example fix
// before: archive built from wrong cwd // tar -czf capsule.tgz ../my-capsule/Capsule.toml (entries contain ..) // after: build from inside the capsule root // cd my-capsule && tar -czf ../capsule.tgz .
Defensive patterns
Strategy: validation
Validate before calling
// inspect the archive before digesting
let out = std::process::Command::new("tar")
.args(["-tzf", archive_path])
.output()?;
let unsafe_entry = String::from_utf8_lossy(&out.stdout)
.lines()
.any(|l| l.starts_with('/') || l.split('/').any(|seg| seg == ".."));
if unsafe_entry { eprintln!("archive has absolute or .. paths; rebuild it"); } Type guard
fn archive_paths_are_safe(names: &[String]) -> bool {
names.iter().all(|n| !n.starts_with('/') && !n.split('/').any(|s| s == ".."))
} Try / catch
match archive_digest_for_source(archive) {
Err(e) if e.to_string().contains("unsafe path") => {
anyhow::bail!("rebuild the archive with relative paths: tar -czf capsule.tgz -C <root> .");
}
other => other,
} Prevention
- Always build archives with `tar -C <capsule-root> .` so entries are relative
- Audit archives with `tar -tzf` before digesting or publishing third-party archives
- Never concatenate or merge archives that may carry absolute prefixes
- Strip common path prefixes when repacking restructured trees
When it happens
Trigger: Calling archive_digest_for_source on a .tar.gz whose entries include absolute paths (e.g. `/etc/passwd`) or `..` components (e.g. `../../evil`), typically produced by packaging tools that used absolute source paths or bad base-directory handling.
Common situations: Hand-built tar archives with `tar -C` mistakes; archives created on Windows with drive-letter absolute paths; a malicious or tampered capsule archive; archives regenerated after directory restructuring without stripping a common prefix.
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
- durable capsule archive contains unsafe path
- capsule archive contains a link or special file {name}
- durable capsule archive contains a link or special file
- malicious shuttle detected: invalid path '{}'
- symlink {} resolves outside the capsule source root ({}); re
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/d7902f48dd12bb78.
Report an issue: GitHub.