From f2e5c31b4a67ae153754fcac37efa931388a5f18 Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:56:22 +0800 Subject: [PATCH] Reject special skill archive members --- .../src/crewai_cli/experimental/skills/main.py | 2 ++ lib/cli/tests/skills/test_safe_extract.py | 15 +++++++++++++++ .../src/crewai/experimental/skills/cache.py | 2 ++ .../tests/experimental/skills/test_cache.py | 15 +++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/lib/cli/src/crewai_cli/experimental/skills/main.py b/lib/cli/src/crewai_cli/experimental/skills/main.py index b671b7733..612b85d82 100644 --- a/lib/cli/src/crewai_cli/experimental/skills/main.py +++ b/lib/cli/src/crewai_cli/experimental/skills/main.py @@ -393,6 +393,8 @@ def _safe_extractall(tf: tarfile.TarFile, dest: Path) -> None: 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. diff --git a/lib/cli/tests/skills/test_safe_extract.py b/lib/cli/tests/skills/test_safe_extract.py index 9761f564d..f1083f5fa 100644 --- a/lib/cli/tests/skills/test_safe_extract.py +++ b/lib/cli/tests/skills/test_safe_extract.py @@ -83,6 +83,21 @@ def test_blocks_hardlink_escaping_destination(tmp_path: Path) -> None: _safe_extractall(tf, dest) +def test_blocks_special_tar_member(tmp_path: Path) -> None: + """Special tar members such as FIFOs are rejected.""" + dest = tmp_path / "dest" + dest.mkdir() + + def build(tf: tarfile.TarFile) -> None: + fifo = tarfile.TarInfo("pipe") + fifo.type = tarfile.FIFOTYPE + tf.addfile(fifo) + + with _tar_from_members(build) as tf: + with pytest.raises(ValueError, match="unsupported tar member"): + _safe_extractall(tf, dest) + + def test_allows_benign_relative_symlink(tmp_path: Path) -> None: """A symlink that stays within dest is permitted.""" dest = tmp_path / "dest" diff --git a/lib/crewai/src/crewai/experimental/skills/cache.py b/lib/crewai/src/crewai/experimental/skills/cache.py index 2d334157a..065c2c521 100644 --- a/lib/crewai/src/crewai/experimental/skills/cache.py +++ b/lib/crewai/src/crewai/experimental/skills/cache.py @@ -142,6 +142,8 @@ def _safe_extractall(tf: tarfile.TarFile, dest: Path) -> None: 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 if os.path.isabs(link_target): diff --git a/lib/crewai/tests/experimental/skills/test_cache.py b/lib/crewai/tests/experimental/skills/test_cache.py index 21b8f2473..b6601dccf 100644 --- a/lib/crewai/tests/experimental/skills/test_cache.py +++ b/lib/crewai/tests/experimental/skills/test_cache.py @@ -170,6 +170,21 @@ def test_safe_extractall_blocks_hardlink_escaping_cache_destination( _safe_extractall(tf, dest) +def test_safe_extractall_blocks_special_cache_tar_member(tmp_path: Path) -> None: + """Special tar members such as FIFOs are rejected.""" + dest = tmp_path / "dest" + dest.mkdir() + + def build(tf: tarfile.TarFile) -> None: + fifo = tarfile.TarInfo("pipe") + fifo.type = tarfile.FIFOTYPE + tf.addfile(fifo) + + with _tar_from_members(build) as tf: + with pytest.raises(ValueError, match="unsupported tar member"): + _safe_extractall(tf, dest) + + def test_safe_extractall_allows_benign_cache_symlink(tmp_path: Path) -> None: """A symlink that stays within dest is permitted.""" dest = tmp_path / "dest"