Keywords: ZipSlip, path traversal, archive extraction, CWE-22, ddev, Go, tar, zip
- Overview
- Vulnerability Details
- Technical Analysis
- Attack Chain
- Impact
- Remediation
- CVSS Metrics
- Timeline
- References
- Contact
- Disclaimer
A ZipSlip path traversal vulnerability exists in ddev/ddev (3K+ stars), a popular open-source local development tool for PHP, Python, and Node.js projects. Both the Untar() and Unzip() functions in pkg/archive/archive.go use filepath.Join(dest, file.Name) without any path containment validation, allowing a crafted archive to write files to arbitrary locations on a developer machine.
ddev downloads and extracts archives from remote sources for add-ons, database imports (ddev import-db), and file imports (ddev import-files), making this a realistic attack vector through supply chain or social engineering scenarios.
| Field | Value |
|---|---|
| Advisory | GHSA-x2xq-qhjf-5mvg |
| CVE | CVE-2026-32885 |
| CWE | CWE-22: Improper Limitation of a Pathname to a Restricted Directory |
| CVSS Score | 6.5 (Moderate) |
| Package | github.com/ddev/ddev (Go) |
| Affected Versions | <= latest (all versions) |
| Patched Version | None (fix in progress) |
| Component | pkg/archive/archive.go |
Both Untar() and Unzip() functions in pkg/archive/archive.go construct output paths by directly joining the destination directory with the archive entry name, without any sanitization or containment check.
pkg/archive/archive.go:235 (Untar):
fullPath := filepath.Join(dest, file.Name) // NO SANITIZATIONpkg/archive/archive.go:342 (Unzip):
fullPath := filepath.Join(dest, file.Name) // NO SANITIZATIONBoth functions then create directories via os.MkdirAll and files via os.Create using the unsanitized path.
Go's filepath.Join() resolves ../ sequences in the joined path. When an archive entry has a name like ../../../tmp/malicious, the resulting fullPath escapes the intended destination directory:
// Example:
// dest = "/safe/extract/dir"
// file.Name = "../../../tmp/malicious"
// filepath.Join(dest, file.Name) = "/tmp/malicious" // ESCAPED!The fix requires a path containment check after filepath.Join:
fullPath := filepath.Join(dest, file.Name)
if !strings.HasPrefix(filepath.Clean(fullPath), filepath.Clean(dest) + string(os.PathSeparator)) {
return fmt.Errorf("entry %q escapes destination directory", file.Name)
}+----------------------------------------------------------+
| 1. CRAFT MALICIOUS ARCHIVE |
| Tar/zip with ../../../ path traversal entries |
+---------------------------+------------------------------+
|
v
+----------------------------------------------------------+
| 2. DELIVER TO DEVELOPER |
| - Malicious ddev add-on |
| - Compromised database dump (ddev import-db) |
| - Trojan file archive (ddev import-files) |
+---------------------------+------------------------------+
|
v
+----------------------------------------------------------+
| 3. DDEV EXTRACTS ARCHIVE |
| Untar()/Unzip() calls filepath.Join(dest, file.Name) |
| No path containment check |
+---------------------------+------------------------------+
|
v
+----------------------------------------------------------+
| 4. ARBITRARY FILE WRITE |
| Files written outside extraction directory |
| Overwrite configs, inject code, plant backdoors |
+----------------------------------------------------------+
| Aspect | Description |
|---|---|
| Direct Impact | Arbitrary file write on developer machines |
| Attack Surface | ddev add-ons, ddev import-db, ddev import-files |
| Supply Chain Risk | Malicious add-ons can overwrite project files or system configs |
| Social Engineering | Database dumps from compromised staging/colleague/client |
| Affected Users | 3K+ GitHub stars, widely used in PHP/Drupal/WordPress development |
The maintainer (rfay) confirmed the risk extends beyond add-on installs to include ddev import-db (user-supplied database archives) and ddev import-files (user-supplied file archives), where developers have no reason to suspect malicious content.
For ddev maintainers:
- Add path containment checks after
filepath.Joinin bothUntar()andUnzip()functions - Verify
filepath.Clean(fullPath)is prefixed byfilepath.Clean(dest)+ path separator - For symlinks in tar archives: resolve link targets and apply the same containment check
- Add tests covering
../traversal in file entries, symlink targets, and absolute symlink targets
For ddev users:
- Only use add-ons from trusted sources
- Only import database dumps and file archives from verified origins
- Monitor for unexpected file changes after running import commands
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector | Network | Malicious archives delivered remotely |
| Attack Complexity | Low | Standard ZipSlip payload construction |
| Privileges Required | None | No authentication needed |
| User Interaction | Required | Developer must extract the archive |
| Scope | Unchanged | Impact stays within developer machine |
| Confidentiality | None | Write-only primitive |
| Integrity | High | Arbitrary file write anywhere on filesystem |
| Availability | None | No denial of service |
| Date | Event |
|---|---|
| 2026-03-10 | Vulnerability reported via GitHub PVRT |
| 2026-03-10 | Maintainer (rfay) acknowledged and confirmed the vulnerability |
| 2026-03-15 | CVE requested by stasadev |
| 2026-03-17 | CVE-2026-32885 assigned by GitHub |
- GHSA-x2xq-qhjf-5mvg
- CVE-2026-32885
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- Snyk ZipSlip Research
- Website: snailsploit.com
- GitHub: @SnailSploit
- LinkedIn: /in/kaiaizen
This advisory is published for educational and defensive purposes under responsible disclosure principles. The information provided is intended to help developers and security teams understand and remediate the vulnerability. Do not use this information for unauthorized testing or malicious purposes.