From c038d7e1eb761131067b55c0591a94155b2c6f83 Mon Sep 17 00:00:00 2001 From: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:13:57 +0800 Subject: [PATCH] fix(cli): reject unsafe plugin archive links --- .../cli/src/agentteams_cli/plugin_manager.py | 2 ++ .../tests/cli/test_agentteams_plugin_cli.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/plugins/cli/src/agentteams_cli/plugin_manager.py b/plugins/cli/src/agentteams_cli/plugin_manager.py index 15153435c..43750089d 100644 --- a/plugins/cli/src/agentteams_cli/plugin_manager.py +++ b/plugins/cli/src/agentteams_cli/plugin_manager.py @@ -67,6 +67,8 @@ def _safe_extract_tar(package: Path, target: Path) -> None: raise ValueError(f"unsafe tar member: {member.name}") if member.name.startswith("/") or ".." in Path(member.name).parts: raise ValueError(f"unsafe tar member: {member.name}") + if not (member.isfile() or member.isdir()): + raise ValueError(f"unsafe tar member type: {member.name}") archive.extractall(target) except tarfile.TarError as exc: raise ValueError(f"invalid tar package: {exc}") from exc diff --git a/plugins/tests/cli/test_agentteams_plugin_cli.py b/plugins/tests/cli/test_agentteams_plugin_cli.py index cee1e0264..8d19beb71 100644 --- a/plugins/tests/cli/test_agentteams_plugin_cli.py +++ b/plugins/tests/cli/test_agentteams_plugin_cli.py @@ -3,6 +3,7 @@ from __future__ import annotations +import io import json import os import subprocess @@ -197,6 +198,30 @@ def test_cli_reports_invalid_package_without_traceback(self) -> None: self.assertIn("ERROR:", result.stdout + result.stderr) self.assertNotIn("Traceback", result.stdout + result.stderr) + def test_cli_rejects_tar_symlink_escape(self) -> None: + package = self.project / "symlink-escape.tar.gz" + outside = self.project / "outside" + outside.mkdir() + payload = b"escaped\n" + + with tarfile.open(package, "w:gz") as archive: + link = tarfile.TarInfo("teamharness/escape") + link.type = tarfile.SYMTYPE + link.linkname = str(outside) + archive.addfile(link) + + member = tarfile.TarInfo("teamharness/escape/pwned.txt") + member.size = len(payload) + archive.addfile(member, io.BytesIO(payload)) + + result = self.run_agentteams( + "plugin", "install", "teamharness", "--package", str(package) + ) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("unsafe tar member", result.stdout + result.stderr) + self.assertFalse((outside / "pwned.txt").exists()) + if __name__ == "__main__": unittest.main(verbosity=2)