{"id":"GHSA-xg9w-vg3g-6m68","summary":"GuardDog Path Traversal Vulnerability Leads to Arbitrary File Overwrite and RCE","details":"## Summary\n\nA **path traversal vulnerability** exists in GuardDog's `safe_extract()` function that allows malicious PyPI packages to write arbitrary files outside the intended extraction directory, leading to **Arbitrary File Overwrite** and **Remote Code Execution** on systems running GuardDog.\n\n**CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)\n\n## Details\n\n### Vulnerable Code\n\n**File:** `guarddog/utils/archives.py`\n\n```python\nelif zipfile.is_zipfile(source_archive):\n    with zipfile.ZipFile(source_archive, \"r\") as zip:\n        for file in zip.namelist():\n            # Note: zip.extract cleans up any malicious file name\n            # such as directory traversal attempts This is not the\n            # case of zipfile.extractall\n            zip.extract(file, path=os.path.join(target_directory, file))  # ❌ VULNERABLE\n```\n\n### Root Cause\n\nThe comment about `zip.extract()` fooled me at first :) then I noticed the `os.path.join()` call. \nThe vulnerability stems from **incorrect usage of Python's `zipfile.ZipFile.extract()` API**:\n\n- The `path` parameter should be the **target directory**, not a full file path\n- `extract()` automatically appends the member name to the path\n- By passing `os.path.join(target_directory, file)`, GuardDog causes the filename to be appended **twice**\n- This breaks zipfile's built-in path traversal sanitization\n\n### Attack Vector\n\n1. Attacker creates malicious wheel with path traversal filenames\n2. Uploads to PyPI or distributes directly\n3. Package scan: `guarddog pypi scan malicious-pkg`\n4. GuardDog downloads and extracts the package\n5. Malicious files written to arbitrary locations\n6. Code execution could be achieved\n\n## Impact\n\nImpact depends on how GuardDog is running and under which environment.\n\n### Critical Scenarios\n\n1. **Immediate Code Execution**\n   - Write to `~/.bashrc` → executes on next shell\n   - Write to `~/.profile` → executes on login\n\n2. **Persistent Backdoors**\n   - Write to `~/.ssh/authorized_keys` → SSH access\n   - Write to `/etc/cron.d/malicious` → scheduled execution (if root)\n   - Write to systemd user services → persistent execution\n\nand more...\n\n## Credits\n\n**Reported by:** Charbel (dwbruijn)","aliases":["CVE-2026-22871","PYSEC-2026-1430"],"modified":"2026-07-07T17:57:32.657033107Z","published":"2026-01-13T21:54:49Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-01-13T21:54:49Z","nvd_published_at":"2026-01-13T21:15:55Z","cwe_ids":["CWE-22"]},"references":[{"type":"WEB","url":"https://github.com/DataDog/guarddog/security/advisories/GHSA-xg9w-vg3g-6m68"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-22871"},{"type":"WEB","url":"https://github.com/DataDog/guarddog/commit/9aa6a725b2c71d537d3c18d1c15621395ebb879c"},{"type":"PACKAGE","url":"https://github.com/DataDog/guarddog"}],"affected":[{"package":{"name":"guarddog","ecosystem":"PyPI","purl":"pkg:pypi/guarddog"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.7.1"}]}],"versions":["0.1.1","0.1.10","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","1.0.0","1.0.1","1.0.2","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.10.0","1.10.1","1.11.0","1.11.1","1.11.2","1.2","1.2.1","1.3.0","1.4.0","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.5.8","1.6.0","1.7.0","1.8.0","1.8.1","1.8.2","1.9.0","2.0.0","2.0.1","2.0.2","2.0.3","2.0.4","2.0.5","2.0.6","2.1.0","2.2.0","2.3.0","2.4.0","2.5.0","2.6.0","2.7.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-xg9w-vg3g-6m68/GHSA-xg9w-vg3g-6m68.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"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"}]}