jdx/mise · error

vfox plugin archive contains a link or special file

Error message

vfox plugin archive contains a link or special file

What it means

When installing or updating a vfox plugin delivered as a packslip-signed archive, mise inspects every tar entry before extraction. Any entry that is a symlink, hardlink, or special file (device, fifo, etc.) is rejected, because such entries can resolve to paths outside the extraction directory during unpacking. This is a security guard against archive-based path escape and file-overwrite attacks.

Source

Thrown at src/plugins/packslip.rs:212

    ensure!(
        artifact.requires.is_none(),
        "vfox plugin artifacts must not declare host requirements"
    );
    ensure!(
        matches!(artifact.format.as_deref(), Some("tar.gz" | "tgz")),
        "vfox plugin artifacts currently require tar.gz format"
    );
    Ok(())
}

/// Reject links and special files before extraction, including links whose
/// targets might otherwise be resolved while unpacking a later archive entry.
pub(crate) fn validate_archive(path: &Path) -> Result<()> {
    let reader = flate2::read::GzDecoder::new(std::fs::File::open(path)?);
    let mut archive = jdx_tar::Archive::new(reader);
    for entry in archive.entries()? {
        let entry = entry?;
        ensure!(
            matches!(
                entry.entry_type(),
                jdx_tar::EntryType::File | jdx_tar::EntryType::Directory
            ),
            "vfox plugin archive contains a link or special file"
        );
        let path = entry.path()?;
        for component in path.components() {
            if component == Component::CurDir {
                continue;
            }
            let Component::Normal(name) = component else {
                bail!("vfox plugin archive contains an unsafe path");
            };
            let name = name.to_string_lossy();
            ensure!(
                !name.contains(['\\', ':'])
                    && !name.eq_ignore_ascii_case(".git")

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Repackage the plugin archive so it contains only regular files and directories (resolve symlinks before tarring, e.g. `tar --dereference`).
  2. Remove any symlinked or special files (sockets, fifos, devices) from the plugin source directory before archiving.
  3. Verify the offending entry with `tar -tvf <archive> | grep -E '^l|^h'` to find links, then fix the packaging script.

Example fix

// before: tar -czf plugin.tar.gz assets/ bin/extra -> bin/extra is a symlink
// after
tar --dereference -czf plugin.tar.gz assets/ bin/
Defensive patterns

Strategy: validation

Validate before calling

# shell: check a plugin tarball for links/special files before packaging
tar -tvf plugin.tar.gz | grep -E '^(l|h|b|c|p)' && echo 'REJECTED: links/special files present' || echo 'OK'

Prevention

When it happens

Trigger: Calling the vfox plugin install path in src/plugins/packslip.rs whose archive contains any tar entry with EntryType other than File or Directory — e.g. a symlinked binary or a hardlinked file inside the plugin tarball.

Common situations: Plugin authors build the archive with a tool that preserves symlinks (e.g. packaging a repo where bin/vfox-plugin points to the real script), or tar packaging picks up git worktree symlinks or special files from the source tree.

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/c31b6e2dddbbf3d9. Report an issue: GitHub.