astrid-runtime/astrid · error
capsule source symlink {} resolves outside source root
Error message
capsule source symlink {} resolves outside source root What it means
Raised by collect_entries during canonical_capsule_archive when a symlink inside the capsule source canonicalizes to a path outside the source root. The library permits file symlinks (to keep npm-style node_modules/.bin working) only if their targets stay within the capsule, preventing archive content that escapes the source tree.
Source
Thrown at crates/astrid-capsule-install/src/storage.rs:789
root: &Path,
current: &Path,
entries: &mut Vec<(PathBuf, Metadata)>,
) -> anyhow::Result<()> {
let mut children = read_dir_sorted(current)?;
for (name, metadata) in children.drain(..) {
let relative = name
.strip_prefix(root)
.map_err(|_| anyhow::anyhow!("capsule path escaped source root"))?
.to_path_buf();
let file_type = metadata.file_type();
if file_type.is_symlink() {
let resolved = fs::canonicalize(&name).with_context(|| {
format!("canonicalize capsule source symlink {}", relative.display())
})?;
let canonical_root = fs::canonicalize(root)
.with_context(|| format!("canonicalize capsule source root {}", root.display()))?;
if !resolved.starts_with(&canonical_root) {
bail!(
"capsule source symlink {} resolves outside source root",
relative.display()
);
}
let resolved_metadata = fs::metadata(&resolved).with_context(|| {
format!("stat capsule source symlink target {}", relative.display())
})?;
if !resolved_metadata.is_file() {
bail!(
"capsule source symlink {} does not resolve to a regular file",
relative.display()
);
}
// File links are materialized as regular archive entries. This
// preserves npm's node_modules/.bin links without ever storing a
// redirect in the durable package.
entries.push((relative, resolved_metadata));
continue;View on GitHub (pinned to affd8760f4)
Solutions
- Move the symlink target inside the capsule source directory and update the link to a relative in-tree path.
- Replace the symlink with a real copy of the target file inside the capsule.
- If the content belongs to another package, publish it separately and depend on it rather than linking.
Example fix
// before: escapes the source root vendor/lib.wit -> ../../shared/lib.wit // after: keep the target inside the capsule vendor/lib.wit (regular file copy of shared/lib.wit)
Defensive patterns
Strategy: validation
Validate before calling
fn symlink_escapes_root(link: &Path, root: &Path) -> std::io::Result<bool> {
let target = std::fs::canonicalize(link)?;
let root = std::fs::canonicalize(root)?;
Ok(!target.starts_with(root))
} Type guard
fn is_in_tree_link(link: &Path, root: &Path) -> bool {
std::fs::canonicalize(link).ok()
.zip(std::fs::canonicalize(root).ok())
.map_or(false, |(t, r)| t.starts_with(r))
} Try / catch
match publish(...) {
Err(e) if e.to_string().contains("resolves outside source root") => { eprintln!("copy linked assets into the capsule"); exit(1); }
other => other?,
} Prevention
- Use relative links whose targets stay inside the capsule
- Copy shared assets into the tree instead of linking ../
- Audit with `find . -type l -exec readlink {} \;` before publishing
When it happens
Trigger: Archiving a capsule whose directory contains a symlink pointing to a file outside the capsule directory (e.g. ../shared/lib.wit or /usr/share/foo); fs::canonicalize resolves it and the starts_with(canonical_root) check fails.
Common situations: Sharing a common asset via ../ links during development; node_modules links hoisted above the package root in monorepos; linking to a globally installed tool or config file.
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
- symlink {} resolves outside the capsule source root ({}); re
- cache contains a redirect or special entry: {}
- capsule materialization destination is not a directory
- materialization parent is a symlink: {}
- legacy capsule component path is not relative
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/ab0e722bef4f9123.
Report an issue: GitHub.