astrid-runtime/astrid · error

capsule archive contains a link or special file {name}

Error message

capsule archive contains a link or special file {name}

What it means

When digesting a capsule archive, each entry's tar entry type is checked: only directories and regular files are allowed. Symlinks, hardlinks, and special files (devices, fifos) are rejected because the canonical archive format does not support them and links could redirect reads outside the intended tree during unpacking.

Source

Thrown at crates/astrid-capsule-install/src/source_digest.rs:58

        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;
        }
        if let Some(parent) = destination.parent() {
            fs::create_dir_all(parent)
                .with_context(|| format!("create capsule archive parent for {name}"))?;
        }
        entry
            .unpack(&destination)
            .with_context(|| format!("unpack capsule archive file {name}"))?;
        // Drain the entry explicitly so malformed/truncated streams fail
        // before the canonical builder reads the staged tree.
        let mut sink = Vec::new();
        entry

View on GitHub (pinned to affd8760f4)

Solutions

  1. Dereference symlinks when packing: `tar -czhf capsule.tgz -C <root> .` (or remove the symlinks and copy real files).
  2. Find offending entries with `tar -tvzf capsule.tgz | grep -E '^l|^[^d-]'` and remove or replace them.
  3. Exclude non-file artifacts from the archive (build sockets, fifos) via --exclude.
  4. Hard-code a packaging step that copies the tree (cp -rL) before archiving.

Example fix

// before: symlink left in tree, archive keeps the link entry
ln -s ../vendor/wit wit; tar -czf capsule.tgz .
// after: dereference links into real files
tar -czhf capsule.tgz -C capsule-root .
Defensive patterns

Strategy: validation

Validate before calling

let out = std::process::Command::new("tar")
    .args(["-tvzf", archive_path])
    .output()?;
let has_links = String::from_utf8_lossy(&out.stdout)
    .lines()
    .any(|l| l.starts_with('l') || l.starts_with('h'));

Type guard

fn only_dirs_and_files(types: &[tar::EntryType]) -> bool {
    types.iter().all(|t| t.is_dir() || t.is_file())
}

Try / catch

match archive_digest_for_source(archive) {
    Err(e) if e.to_string().contains("link or special file") => {
        anyhow::bail!("repack with dereferenced symlinks: tar -czhf capsule.tgz -C <root> .");
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling archive_digest_for_source on a .tar.gz containing a symlink, hardlink, or special-file entry — e.g. archives built with `tar -h` disabled over trees containing symlinks, or archives capturing dev/fifo nodes.

Common situations: Project trees containing symlinks (node_modules links, vendored deps linked in) archived without dereferencing; archives made from a build directory containing sockets/fifos; hand-crafted archives with hardlinks for space savings.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/2cbf2b8234415edb. Report an issue: GitHub.