{"record":{"id":"a0818932d299785f","repo":"crewAIInc/crewAI","slug":"blocked-path-traversal-attempt-member-name-r","errorCode":null,"errorMessage":"Blocked path traversal attempt: {member.name!r}","messagePattern":"Blocked path traversal attempt: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"lib/cli/src/crewai_cli/skills/main.py","lineNumber":438,"sourceCode":"            return None\n\n\ndef _safe_extractall(tf: tarfile.TarFile, dest: Path) -> None:\n    \"\"\"Path-traversal-safe extraction for Python versions without tar filters.\n\n    Validates both the member's own path and, for symlink/hardlink members,\n    the link target. Without the link-target check a malicious archive can\n    plant a symlink that escapes ``dest`` (e.g. ``link -> /home/user/.ssh``)\n    followed by a regular member written *through* that link\n    (``link/authorized_keys``), escaping ``dest`` even though every member\n    name resolves inside it. This mirrors the protection that\n    ``tarfile.extractall(..., filter=\"data\")`` provides when available.\n    \"\"\"\n    dest_resolved = dest.resolve()\n    for member in tf.getmembers():\n        member_path = (dest / member.name).resolve()\n        if not member_path.is_relative_to(dest_resolved):\n            raise ValueError(f\"Blocked path traversal attempt: {member.name!r}\")\n        if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):\n            raise ValueError(f\"Blocked unsupported tar member: {member.name!r}\")\n        if member.issym() or member.islnk():\n            link_target = member.linkname\n            # Absolute link targets always escape the destination.\n            if os.path.isabs(link_target):\n                raise ValueError(\n                    f\"Blocked link target escaping destination: \"\n                    f\"{member.name!r} -> {link_target!r}\"\n                )\n            # Hardlink names are relative to the archive root; symlink\n            # targets are relative to the member's own directory.\n            anchor = dest if member.islnk() else (dest / member.name).parent\n            resolved_target = (anchor / link_target).resolve()\n            if not resolved_target.is_relative_to(dest_resolved):\n                raise ValueError(\n                    f\"Blocked link target escaping destination: \"\n                    f\"{member.name!r} -> {link_target!r}\"","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/cli/src/crewai_cli/skills/main.py#L420-L456","documentation":"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).","triggerScenarios":"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.","commonSituations":"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).","solutions":["Do not install the skill — report the malicious archive to the registry/org owners.","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.","Audit the archive manually before any override: download it and run `tar -tzf` to list member names."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import tarfile\nfrom pathlib import Path\n\ndef archive_is_safe(path: str, sample_dest: Path) -> bool:\n    dest = sample_dest.resolve()\n    with tarfile.open(path) as tf:\n        for m in tf.getmembers():\n            if not (dest / m.name).resolve().is_relative_to(dest):\n                return False\n    return True","typeGuard":null,"tryCatchPattern":"from crewai_cli.skills.main import _safe_extract_tar\nimport tarfile\n\ntry:\n    with tarfile.open(archive) as tf:\n        _safe_extract_tar(tf, dest)\nexcept ValueError as exc:\n    if \"path traversal\" in str(exc):\n        quarantine_archive_and_report(ref)  # never extract manually as a workaround\n    raise","preventionTips":["Never bypass or monkey-patch the safe-extract guards.","Prefer `tar -tvzf` / `zipfile.namelist()` inspection of third-party archives before installing.","Report archives with traversal paths to the registry maintainers; do not 'fix' by extracting by hand."],"tags":["security","path-traversal","tar","archive","skill","install"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}