{"record":{"id":"827c1cd8f6bf4195","repo":"crewAIInc/crewAI","slug":"blocked-unsupported-tar-member-member-name-r","errorCode":null,"errorMessage":"Blocked unsupported tar member: {member.name!r}","messagePattern":"Blocked unsupported tar member: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"lib/cli/src/crewai_cli/skills/main.py","lineNumber":440,"sourceCode":"\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}\"\n                )\n    tf.extractall(dest)  # noqa: S202","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/cli/src/crewai_cli/skills/main.py#L422-L458","documentation":"`_safe_extract_tar` only permits regular files, directories, symlinks, and hardlinks. Any other tar member type — device nodes (character/block), FIFOs — is rejected with `ValueError` before extraction, because such members have no legitimate purpose in a skill archive and are a classic tar-bomb primitive.","triggerScenarios":"Installing a skill archive containing a device member (e.g. `/dev/null` style char device created by a poorly configured `tar -czf /dev/...`) or a FIFO. The `not (member.isfile() or member.isdir() or member.issym() or member.islnk())` branch fires.","commonSituations":"A skill author accidentally tarred device files or special nodes from their filesystem; deliberately malicious archives trying to plant devices; rare GNU tar options that store sparseness/special types the check does not whitelist.","solutions":["Do not install this archive; report it to the registry maintainers if it came from the registry.","If you authored the skill, rebuild the tarball from a clean directory containing only regular files/dirs (e.g. re-run `crewai skill publish` from a sanitized folder).","Inspect with `tar -tvzf archive.tar.gz` to find the offending member."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import tarfile\n\ndef only_supported_members(path: str) -> bool:\n    with tarfile.open(path) as tf:\n        return all(m.isfile() or m.isdir() or m.issym() or m.islnk() for m in tf.getmembers())","typeGuard":null,"tryCatchPattern":"try:\n    _safe_extract_tar(tf, dest)\nexcept ValueError as exc:\n    if \"unsupported tar member\" in str(exc):\n        log_security_event(f\"non-file member in skill archive: {exc}\")\n        delete_archive()\n    raise","preventionTips":["Build skill archives from clean directories containing only regular files and folders.","Run `tar -tvzf` on your own archives before publishing to catch device/FIFO members.","Treat unsupported-member hits on third-party skills as supply-chain red flags."],"tags":["security","tar","archive","validation","install","skill"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}