CVE-2026-22871

The Watchdog Bites Back: CVE-2026-22871 in DataDog GuardDog

Alon Barad
Alon Barad
Software Engineer

Jan 14, 2026·5 min read

Executive Summary (TL;DR)

GuardDog, a tool meant to identify malicious Python packages, contained a logic flaw in how it extracted ZIP archives. By misusing `os.path.join` and `zipfile.extract`, it allowed malicious packages to break out of the temporary sandbox and write files anywhere on the file system. This grants attackers RCE on the machines of security researchers and CI/CD pipelines scanning their code.

A critical path traversal vulnerability in GuardDog's archive extraction logic allows malicious PyPI packages to overwrite arbitrary files on the host system, turning the security scanner into an attack vector.

The Hook: When the Shield Becomes the Sword

In the world of supply chain security, we build tools to gaze into the abyss so you don't have to. GuardDog is one of those tools—a CLI utility from DataDog designed to sniff out malicious PyPI packages before they wreck your environment. It effectively creates a sandbox, rips open the package, and looks for signs of evil. It's the digital equivalent of a bomb disposal robot.

But here is the dark irony: in versions prior to 2.7.1, the bomb disposal robot was rigged to explode the moment it touched a specific type of bomb. CVE-2026-22871 is a path traversal vulnerability that turns GuardDog against its master. Instead of protecting you from malware, GuardDog effectively executes the malware's deployment phase simply by scanning it.

This isn't just a bug; it's a perfect example of how security tooling itself is a high-value target. If I'm an attacker, why try to phish a developer when I can just upload a "test" package to PyPI that compromises the very security researchers analyzing it?

The Flaw: A Tale of Two Paths

The vulnerability lies in guarddog/utils/archives.py, specifically within the safe_extract() function. The developers needed to unzip Python packages (which are just ZIP files) to a temporary directory for analysis. To do this, they iterated through the files in the archive and extracted them one by one.

However, they committed a cardinal sin of Python development: blindly trusting os.path.join(). In Python, os.path.join(path, *paths) has a sharp edge. If any component in *paths is an absolute path (e.g., /etc/passwd or C:\Windows), it discards all previous components. It assumes that if you provided an absolute path, you meant it.

Furthermore, the developers passed this tainted path directly to zipfile.ZipFile.extract(). Now, extract() does its own sanitization, but only on the filename relative to the target directory. It assumes the path argument passed to it (the target directory) is trusted. By calculating the path before calling extract, GuardDog accidentally treated the untrusted filename from the ZIP as the trusted destination directory. It's like checking someone's ID at the front gate, but then letting them choose which room they want to be locked in—including the master bedroom.

The Code: The Smoking Gun

Let's look at the diff. It is a terrifyingly simple logic error. The code iterates over every file in the ZIP archive and decides where to put it.

The Vulnerable Code:

# loops through all files in the zip
zip.extract(file, path=os.path.join(target_directory, file))

Here, file is the name of the entry inside the malicious ZIP (controlled by the attacker). If file is /tmp/pwned, os.path.join ignores target_directory entirely and returns /tmp/pwned. The zip.extract method then receives /tmp/pwned as the directory argument.

The Fix (Commit 9aa6a72):

# The fix is to trust the library, not the math
zip.extract(file, path=target_directory)

By simply passing the target_directory as the root, the zipfile library takes over responsibility. It looks at file, sees it has absolute paths or traversal characters, and sanitizes them relative to target_directory. The fix wasn't about adding more regex; it was about understanding the API contract.

The Exploit: Dropping the Payload

Exploiting this is trivial and requires no race conditions or complex memory corruption. We just need to craft a valid ZIP file that lies about its contents.

Step 1: The Setup We create a ZIP file (let's call it payload.whl). We edit the ZIP headers manually or use a python script to insert a file entry with a malicious name. We don't want a standard file; we want a file named with directory traversal characters.

Step 2: The Path Injection We name our file entry ../../../../home/user/.ssh/authorized_keys. Or, thanks to the os.path.join quirk, we can just name it /home/user/.ssh/authorized_keys.

Step 3: The Trigger The victim runs guarddog scan payload.whl.

  1. GuardDog creates a temp dir: /tmp/guarddog_scan/.
  2. It reads our file entry: /home/user/.ssh/authorized_keys.
  3. It calls os.path.join('/tmp/guarddog_scan/', '/home/user/.ssh/authorized_keys').
  4. Python returns /home/user/.ssh/authorized_keys.
  5. GuardDog calls zip.extract(..., path='/home/user/.ssh/authorized_keys').

Step 4: Game Over The library writes our public key content into the victim's SSH directory. The next time we SSH into the victim's machine, we are logged in as them.

The Impact: Who Watches the Watchmen?

The severity here is High (8.7) because of the context. This tool is used in CI/CD pipelines to gatekeeping releases. If an attacker can compromise the pipeline scanner, they can potentially inject code into the software being built, leading to a massive supply chain attack.

For individual researchers, it means that analyzing malware is now a high-risk activity (more than usual). If you downloaded a suspicious package to check it with GuardDog, you just gave that package RCE on your workstation. The vulnerability requires no authentication and happens automatically upon scanning.

Fix Analysis (1)

Technical Appendix

CVSS Score
8.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Affected Systems

GuardDog CLI < 2.7.1CI/CD pipelines using GuardDog for security scanningDeveloper workstations scanning untrusted PyPI packages

Affected Versions Detail

Product
Affected Versions
Fixed Version
GuardDog
DataDog
< 2.7.12.7.1
AttributeDetail
CWE IDCWE-22
Attack VectorNetwork / Local (Malicious File)
CVSS v4.08.7 (High)
ImpactArbitrary File Overwrite / RCE
Vulnerable APIzipfile.ZipFile.extract
Patch StatusFixed in 2.7.1
CWE-22
Path Traversal

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Vulnerability Timeline

Fix commit authored
2026-01-05
PR merged into main
2026-01-09
CVE Published and Advisory Released
2026-01-13

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.