zed-industries/zed · error · ValueError

archive links are not supported: {member.name}

Error message

archive links are not supported: {member.name}

What it means

safe_extract_archive() deliberately refuses tar members that are symlinks or hardlinks (member.issym()/islnk()). Links are the classic vehicle for tar-extraction attacks — a link pointing outside the destination with a later member writing through it — so the helper rejects any archive containing them rather than trying to resolve them safely.

Source

Thrown at crates/eval_cli/zed_eval/common.py:48

def write_json(path: pathlib.Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")


def load_json(path: pathlib.Path) -> dict[str, Any] | None:
    try:
        data = json.loads(path.read_text())
    except (OSError, json.JSONDecodeError):
        return None
    return data if isinstance(data, dict) else None


def safe_extract_archive(archive: tarfile.TarFile, destination: pathlib.Path) -> None:
    destination = destination.resolve()
    members = archive.getmembers()
    for member in members:
        if member.issym() or member.islnk():
            raise ValueError(f"archive links are not supported: {member.name}")
        target = (destination / member.name).resolve()
        if destination != target and destination not in target.parents:
            raise ValueError(f"archive member escapes destination: {member.name}")
    archive.extractall(destination, members=members)


def command_exists(name: str) -> bool:
    return shutil.which(name) is not None


def run_command(
    command: list[str], *, capture: bool = False
) -> subprocess.CompletedProcess[str]:
    return subprocess.run(command, check=True, text=True, capture_output=capture)


def dedupe_preserving_order(values: list[str]) -> list[str]:
    seen = set()

View on GitHub (pinned to bc538def45)

Solutions

  1. Repack with dereferenced files: `tar --dereference --hard-dereference -czf bundle.tar.gz dir/`
  2. Remove incidental links from the source tree before packing
  3. If links are essential, extract manually after reviewing every link target

Example fix

# before
tar czf bundle.tar.gz repo/          # symlinks preserved → ValueError

# after
tar --dereference --hard-dereference -czf bundle.tar.gz repo/
Defensive patterns

Strategy: validation

Validate before calling

import tarfile
with tarfile.open(archive_path) as archive:
    links = [m.name for m in archive.getmembers() if m.issym() or m.islnk()]
if links:
    raise SystemExit(f"archive contains links: {links[:5]}; repack with tar --dereference")

Type guard

import tarfile

def is_link_free(members):
    return not any(m.issym() or m.islnk() for m in members)

Try / catch

try:
    safe_extract_archive(archive, destination)
except ValueError as e:
    raise SystemExit(f"unsafe archive rejected: {e}")

Prevention

When it happens

Trigger: Extracting an archive created with plain `tar czf` over a tree containing symlinks (node_modules, doc-compression links, linked repo assets) or hardlinks (nix store, pnpm content-addressed stores) — the links become members and trip the check.

Common situations: Bundling dependency trees or repo snapshots with links; archives produced on systems that hardlink duplicate files; hand-rolled packaging scripts.

Related errors


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/a4d807a0e8a7e71a. Report an issue: GitHub.