crewAIInc/crewAI · critical · ValueError

Blocked path traversal attempt: {member.name!r}

Error message

Blocked path traversal attempt: {member.name!r}

What it means

A defense inside `_safe_extract_tar`: for each tar member, `(dest / member.name).resolve()` must stay inside `dest.resolve()`. A member name like `../../etc/passwd` or an absolute path resolves outside the destination, so extraction is blocked with `ValueError` before `tf.extractall` runs. This protects `crewai skill install` from malicious registry archives (Zip Slip / tar traversal).

Source

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

            return None


def _safe_extractall(tf: tarfile.TarFile, dest: Path) -> None:
    """Path-traversal-safe extraction for Python versions without tar filters.

    Validates both the member's own path and, for symlink/hardlink members,
    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}"

View on GitHub (pinned to 754d7323be)

Solutions

  1. Do not install the skill — report the malicious archive to the registry/org owners.
  2. If the failure is due to your own `skills/` dir being a symlink, install into a real directory (or make the symlink target the final location) and retry.
  3. Audit the archive manually before any override: download it and run `tar -tzf` to list member names.
Defensive patterns

Strategy: try-catch

Validate before calling

import tarfile
from pathlib import Path

def archive_is_safe(path: str, sample_dest: Path) -> bool:
    dest = sample_dest.resolve()
    with tarfile.open(path) as tf:
        for m in tf.getmembers():
            if not (dest / m.name).resolve().is_relative_to(dest):
                return False
    return True

Try / catch

from crewai_cli.skills.main import _safe_extract_tar
import tarfile

try:
    with tarfile.open(archive) as tf:
        _safe_extract_tar(tf, dest)
except ValueError as exc:
    if "path traversal" in str(exc):
        quarantine_archive_and_report(ref)  # never extract manually as a workaround
    raise

Prevention

When it happens

Trigger: Installing a skill whose tarball contains a member such as `../../../.ssh/authorized_keys`, `/etc/cron.d/x`, or any path whose resolved location escapes the install directory. Note the resolve() also follows symlinks already on disk inside dest, so a previously-extracted symlink can make a later member trip this check.

Common situations: Compromised or hand-crafted skill archives served by (or injected into) the registry; MITM tampering with the download_url payload; benign edge cases where dest itself contains symlinks pointing outside (e.g. skills dir symlinked into a dotfiles repo).

Related errors


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