crewAIInc/crewAI · critical · ValueError

Blocked link target escaping destination: {member.name!r} ->

Error message

Blocked link target escaping destination: {member.name!r} -> {link_target!r}

What it means

First of two link-target checks in `_safe_extract_tar`: a symlink or hardlink member whose `linkname` is an absolute path (e.g. `link -> /home/user/.ssh`) is rejected immediately, because an absolute link always points outside the extraction destination regardless of resolution.

Source

Thrown at lib/cli/src/crewai_cli/skills/main.py:445

    the link target. Without the link-target check a malicious archive can
    plant a symlink that escapes ``dest`` (e.g. ``link -> /home/user/.ssh``)
    followed by a regular member written *through* that link
    (``link/authorized_keys``), escaping ``dest`` even though every member
    name resolves inside it. This mirrors the protection that
    ``tarfile.extractall(..., filter="data")`` provides when available.
    """
    dest_resolved = dest.resolve()
    for member in tf.getmembers():
        member_path = (dest / member.name).resolve()
        if not member_path.is_relative_to(dest_resolved):
            raise ValueError(f"Blocked path traversal attempt: {member.name!r}")
        if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
            raise ValueError(f"Blocked unsupported tar member: {member.name!r}")
        if member.issym() or member.islnk():
            link_target = member.linkname
            # Absolute link targets always escape the destination.
            if os.path.isabs(link_target):
                raise ValueError(
                    f"Blocked link target escaping destination: "
                    f"{member.name!r} -> {link_target!r}"
                )
            # Hardlink names are relative to the archive root; symlink
            # targets are relative to the member's own directory.
            anchor = dest if member.islnk() else (dest / member.name).parent
            resolved_target = (anchor / link_target).resolve()
            if not resolved_target.is_relative_to(dest_resolved):
                raise ValueError(
                    f"Blocked link target escaping destination: "
                    f"{member.name!r} -> {link_target!r}"
                )
    tf.extractall(dest)  # noqa: S202


def _safe_extract_zip(zf: zipfile.ZipFile, dest: Path) -> None:
    """Path-traversal-safe ZIP extraction."""
    dest_resolved = dest.resolve()

View on GitHub (pinned to 754d7323be)

Solutions

  1. Do not install the skill; report the archive (org/name and version) to registry maintainers as a potential supply-chain attack.
  2. If you built the archive yourself, recreate it without absolute-path links — store copies of files instead of links.
  3. Verify with `tar -tvzf` that links are relative before publishing skills.
Defensive patterns

Strategy: try-catch

Validate before calling

import tarfile, os

def no_absolute_link_targets(path: str) -> bool:
    with tarfile.open(path) as tf:
        return all(
            not (m.issym() or m.islnk()) or not os.path.isabs(m.linkname)
            for m in tf.getmembers()
        )

Try / catch

try:
    _safe_extract_tar(tf, dest)
except ValueError as exc:
    if "link target escaping" in str(exc):
        report_malicious_archive(org, name, version)
        raise RuntimeError(f"refusing to install {ref}: {exc}") from exc
    raise

Prevention

When it happens

Trigger: Installing an archive containing a member like `symlink: current -> /etc/passwd` or a hardlink whose linkname starts with `/`. Detected by `os.path.isabs(link_target)` on `member.linkname` for `issym()`/`islnk()` members.

Common situations: Malicious archives trying to alias registry content onto system paths; archives built on machines where tar recorded absolute paths for legitimately-linked files; supply-chain attacks distributed via the skill registry.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/5a72c4a56b6c8095. Report an issue: GitHub.