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
- 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.
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
- 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.
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
- Blocked path traversal attempt: {member.name!r}
- Blocked unsupported tar member: {member.name!r}
- Blocked path traversal attempt: {member!r}
- Invalid skill reference: org and name must be single, non-em
- [tool.crewai] definition must resolve inside the project roo
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/5a72c4a56b6c8095.
Report an issue: GitHub.