astrid-runtime/astrid · error

durable capsule archive contains a link or special file

Error message

durable capsule archive contains a link or special file

What it means

read_archive_files found a tar entry whose type is neither a regular file nor a directory — e.g. a symlink, hardlink, fifo, or device node. The library refuses such entries because links and special files can redirect reads/writes outside the capsule content and have no place in a deterministic capsule archive.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:377

    let mut files = std::collections::BTreeMap::new();
    let mut directories = std::collections::BTreeSet::new();
    for entry in archive.entries().context("read durable capsule archive")? {
        let mut entry = entry.context("read durable capsule archive entry")?;
        let path = entry.path().context("read durable capsule archive path")?;
        if path.is_absolute()
            || path.components().any(|component| {
                matches!(
                    component,
                    std::path::Component::ParentDir | std::path::Component::RootDir
                )
            })
        {
            bail!("durable capsule archive contains unsafe path");
        }

        let entry_type = entry.header().entry_type();
        if !entry_type.is_dir() && !entry_type.is_file() {
            bail!("durable capsule archive contains a link or special file");
        }

        let name = path
            .to_str()
            .ok_or_else(|| anyhow::anyhow!("durable capsule archive path is not UTF-8"))?
            .replace('\\', "/");
        if files.contains_key(&name) || directories.contains(&name) {
            bail!("durable capsule archive contains duplicate path {name}");
        }
        if entry_type.is_dir() {
            if !directories.insert(name) {
                bail!("durable capsule archive contains duplicate directory path");
            }
            continue;
        }

        let mut bytes = Vec::new();
        entry

View on GitHub (pinned to affd8760f4)

Solutions

  1. Rebuild the archive dereferencing symlinks (--dereference / follow-links) so only regular files are stored.
  2. Remove symlinks/special files from the source tree before packaging (vendor or inline their content).
  3. Configure the archiver to skip non-regular entry types.
  4. Reject the archive and ask the publisher to fix their packaging.

Example fix

// before: tar preserves symlinks
tar -czf capsule.tgz -C src .            # keeps symlinks
// after: dereference links when archiving
tar -czf capsule.tgz -C src --dereference .
Defensive patterns

Strategy: validation

Validate before calling

let entry_type = entry.header().entry_type();
if !entry_type.is_dir() && !entry_type.is_file() {
    return Err("archive entry is a link or special file");
}

Type guard

fn is_regular_entry(h: &tar::Header) -> bool {
    let t = h.entry_type();
    t.is_file() || t.is_dir()
}

Try / catch

match read_verified_durable_package_for_owner(&store, owner, id).await {
    Ok(pkg) => pkg,
    Err(e) if e.to_string().contains("link or special file") => {
        // rebuild archive with --dereference or without symlinks
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_archive_files on an archive containing symlink/hardlink/device entries; entry_type().is_dir() and is_file() both false triggers the bail.

Common situations: Source tree containing symlinks (node_modules links, docs symlinks) archived with tar's default link preservation; packaging on Unix where symlinks are common; archives produced by tools that include metadata nodes.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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