From db35bd5b630fca6ab59c194cc786ff1757e99690 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 09:22:39 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20path=20traversal=20in=20skill=20zip=20extraction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added validation using `os.path.abspath` and lexical string checks to ensure all files in the archive resolve to a path within the intended extraction directory before calling `extractall` in `helpers/skills_import.py`. Co-authored-by: thirdeyenation <133812267+thirdeyenation@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ helpers/skills_import.py | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000000..4bd17a68ce --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2023-10-27 - Path traversal in skill zip extraction +**Vulnerability:** Path traversal vulnerability due to unchecked zip file extraction. +**Learning:** `zipfile.ZipFile.extractall()` is vulnerable to directory traversal attacks if the zip file contains relative paths like `../`. +**Prevention:** Always validate every path in the archive using `os.path.abspath` and `str.startswith` instead of `.resolve()` or `.is_relative_to()` before extracting to prevent path traversal while maintaining performance. diff --git a/helpers/skills_import.py b/helpers/skills_import.py index c73ffa55de..053c074c67 100644 --- a/helpers/skills_import.py +++ b/helpers/skills_import.py @@ -94,6 +94,12 @@ def _unzip_to_temp_dir(zip_path: Path) -> Path: target.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(zip_path, "r") as z: + target_abs = os.path.abspath(str(target)) + target_prefix = target_abs + ("" if target_abs.endswith(os.sep) else os.sep) + for member in z.namelist(): + member_path = os.path.abspath(os.path.join(target_abs, member)) + if not member_path.startswith(target_prefix): + raise ValueError(f"Unsafe path in archive: {member}") z.extractall(target) # If zip contains a single top-level folder, treat that as the root