{"record":{"id":"5a72c4a56b6c8095","repo":"crewAIInc/crewAI","slug":"blocked-link-target-escaping-destination-member","errorCode":null,"errorMessage":"Blocked link target escaping destination: {member.name!r} -> {link_target!r}","messagePattern":"Blocked link target escaping destination: (.+?) -> (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"lib/cli/src/crewai_cli/skills/main.py","lineNumber":445,"sourceCode":"    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\n\n\ndef _safe_extract_zip(zf: zipfile.ZipFile, dest: Path) -> None:\n    \"\"\"Path-traversal-safe ZIP extraction.\"\"\"\n    dest_resolved = dest.resolve()","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/cli/src/crewai_cli/skills/main.py#L427-L463","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Do not install the skill; report the archive (org/name and version) to registry maintainers as a potential supply-chain attack.","If you built the archive yourself, recreate it without absolute-path links — store copies of files instead of links.","Verify with `tar -tvzf` that links are relative before publishing skills."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import tarfile, os\n\ndef no_absolute_link_targets(path: str) -> bool:\n    with tarfile.open(path) as tf:\n        return all(\n            not (m.issym() or m.islnk()) or not os.path.isabs(m.linkname)\n            for m in tf.getmembers()\n        )","typeGuard":null,"tryCatchPattern":"try:\n    _safe_extract_tar(tf, dest)\nexcept ValueError as exc:\n    if \"link target escaping\" in str(exc):\n        report_malicious_archive(org, name, version)\n        raise RuntimeError(f\"refusing to install {ref}: {exc}\") from exc\n    raise","preventionTips":["Never publish archives containing absolute-path links; store copies instead.","Audit third-party archives for absolute linknames before installing.","Automate a namelist/linkname lint step in skill CI."],"tags":["security","symlink","tar","path-traversal","install","skill"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}