Like a customs form listing books while the hidden compartment holds contraband — this ZIP says one thing to inspectors and another to the unpacker
CVE-2026-115905 affects the zip crate for Rust (zip-rs/zip2), the #1 compression library on crates.io with over 305 million total downloads and 8,505 dependent crates. The vulnerability is a parser differential dubbed *Zip Cloaking*: through version 9.0.4, ZipArchive reads filenames from the Central Directory File Header while ZipStreamReader in src/read/stream.rs reads filenames from the Local File Header — and the crate never checks that the two agree. An attacker crafts a ZIP where the central directory shows innocent names like data/readme.txt while the local headers contain path-traversal payloads like ../../.bashrc or ../../.config/systemd/user/backdoor.service. Any security tool or application code that inspects via central directory sees a clean archive; the streaming extractor then writes attacker-chosen files to attacker-chosen paths. All versions prior to 9.0.5 are affected.
The CNA (GitHub) assigned a CVSS 4.0 score of 10.0, which is a significant overstatement of real-world severity. A 10.0 implies unauthenticated, zero-click, full-scope compromise of any reachable target — that does not describe a library-level file-overwrite bug that requires (a) untrusted ZIP input reaching the application, (b) the application specifically using ZipStreamReader rather than the far more common ZipArchive API, and (c) the overwritten files to be security-relevant enough to escalate to code execution. The vulnerability is real, the cloaking bypass is clever, and the install base is enormous — but the direct impact is file overwrite, not automatic RCE. NVD has not yet published its own analysis. noisgate assesses this at HIGH / 7.5.
4 steps from start to impact.
Craft Cloaked ZIP Archive
data/report.txt) while the corresponding Local File Header entries contain malicious path-traversal filenames (e.g., ../../.bashrc or ../../../etc/cron.d/backdoor). The payload content is placed in the local entries. This is trivial with a hex editor, a Python script using the zipfile module, or any tool that allows raw ZIP header manipulation — the ZIP format is fully documented in APPNOTE.TXT.- Knowledge of ZIP binary format (publicly documented in APPNOTE.TXT)
- Knowledge of target filesystem layout for effective path traversal
- Extremely low friction — ZIP header manipulation is well-understood and requires no specialized tooling
- Attacker needs some knowledge of the target extraction path to craft useful traversal payloads
Deliver Archive to Target Application
zip crate < 9.0.5. Delivery vectors include file upload endpoints, CI/CD artifact ingestion pipelines, email attachment processors, or any workflow that downloads and extracts ZIP content from a semi-trusted or untrusted source. The archive is syntactically valid, so standard upload validation (MIME type checks, file-size limits) will not reject it.- Target application accepts ZIP files from untrusted or semi-trusted sources
- Target application depends on zip crate version < 9.0.5
- Attacker must identify a target that accepts untrusted ZIP input AND uses the Rust zip crate specifically
- Many Rust applications use alternative archive libraries (flate2, tar, async-zip) or shell out to system unzip
Archive Passes Security Inspection
ZipArchive::file_names() and most security scanners), all listed filenames appear benign. The malicious local header filenames are invisible to this inspection path. This is the core of the Zip Cloaking technique — it specifically defeats the inspect-then-extract pattern when different APIs handle each step.- Security inspection relies on central directory metadata (standard behavior)
- No cross-validation between local and central headers is performed
- If the application does NOT perform pre-extraction inspection, this step is irrelevant — the cloaking bypass adds no value though the attack still works
- Some hardened ZIP processors (e.g., Go's archive/zip) already cross-check headers and would reject the archive
Streaming Extractor Writes Attacker-Chosen Paths
ZipStreamReader::extract() in src/read/stream.rs uses the Local File Header filename without reconciling it against the central directory. It writes each entry to the path specified in the local header, enabling path traversal outside the intended extraction directory. The attacker's payload files overwrite targets like shell profiles, cron jobs, systemd units, CI pipeline configs (.github/workflows/, Makefile, build.rs), or application configuration, potentially escalating to code execution when the overwritten file is next invoked.- Application uses ZipStreamReader (streaming API) for extraction, not ZipArchive
- Extraction directory permissions allow writing to traversal target paths
- No post-extraction path validation constrains output files to the intended directory
- ZipStreamReader is the streaming API, used primarily when input is non-seekable (pipes, network streams) — ZipArchive (seekable file/buffer) is far more commonly used in practice
- Modern Rust applications often run in containers or sandboxed environments limiting path traversal impact
- The process must have filesystem permissions to overwrite the targeted sensitive files
cargo update -p zip and verify with cargo tree -i zip. The fix has been available since 2026-10-17, nine days before CVE publication. Per noisgate mitigation SLA for HIGH, deploy within 30 days; per noisgate remediation SLA, complete across all projects within 180 days.ZipStreamReader but the input is actually a file or in-memory buffer (seekable), switch to ZipArchive which uses central directory filenames for extraction and is not affected by this specific differential. This eliminates the vulnerable code path entirely without requiring a crate version upgrade. Audit your codebase with grep -rn 'ZipStreamReader' src/ to find call sites. Deploy within 30 days per noisgate mitigation SLA for HIGH.std::fs::canonicalize(), then assert the output starts with the root. This catches path traversal regardless of how filenames are sourced. Deploy within 30 days per noisgate mitigation SLA.tempdir crate or mktemp -d) with restricted permissions, inside a container or chroot that is not colocated with sensitive files. Even if path traversal escapes the intended target, it cannot reach shell profiles, cron directories, CI configs, or application code. Deploy within 30 days per noisgate mitigation SLA.- WAF / Network IDS — The cloaked ZIP is a syntactically valid archive. No network signature can distinguish it from a benign ZIP file because header divergence is invisible at the transport layer.
- Antivirus / malware scanning of archive contents — Zip Cloaking is *designed* to defeat scanners that inspect central directory metadata. The malicious filenames exist only in local headers, which most AV engines do not cross-reference against the central directory.
- SBOM inventory alone — An SBOM will list
zipas a dependency and flag the version, but cannot determine whether your code actually callsZipStreamReader::extract()vsZipArchive::extract(). You need code-level grep or call-graph analysis to determine reachability of the vulnerable API. - Pinning to an older major version — All versions < 9.0.5 are affected, including the entire 0.x through 8.x series. There is no safe older version to pin to.
The supporting signals.
| In-the-Wild Exploitation | No evidence of active exploitation as of 2026-10-27. Not listed in CISA KEV. No known campaigns. The vulnerability is 1 day old (disclosed 2026-10-26). |
|---|---|
| Proof of Concept | No public PoC indexed on pocindex.io, GitHub, ExploitDB, or Metasploit as of 2026-10-27. However, crafting a cloaked ZIP is trivial — Python's zipfile module or a hex editor suffices. Expect PoCs within days given the simplicity of the technique and public documentation of ZIP header manipulation. |
| EPSS Score | Not yet scored (disclosed 2026-10-26; EPSS typically lags 1–2 weeks for new CVEs). Given library-level scope and no known exploitation, expect low-to-moderate EPSS once scored. |
| KEV Status | Not listed in CISA Known Exploited Vulnerabilities catalog. No federal mandate for emergency patching. |
| CVSS | CNA (GitHub) assigned CVSS 4.0: 10.0 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:L). No CVSS 3.1 vector published. NVD status: Awaiting Analysis. noisgate considers the 10.0 a significant overstatement for a library-level file-overwrite vulnerability. |
| Affected Versions | zip crate (zip-rs/zip2) all versions < 9.0.5 — this includes every release from the 0.x series through 9.0.4. |
| Fixed Version | 9.0.5 (released 2026-10-17, fix commit 8068ab4). Also fixed in 9.1.0+. |
| Ecosystem Exposure | #1 Compression crate on crates.io — 305M+ total downloads, ~22.6M downloads/month, depended on by 8,505 crates (3,299 directly). Renovate and Dependabot PRs already rolling across GitHub. |
| Disclosure Timeline | Advisory GHSA-m665-v7pm-7grj published 2026-10-17. CVE-2026-115905 published 2026-10-26. Fix (v9.0.5) released 2026-10-17, nine days before CVE publication. |
| CWE Classification | CWE-20 (Improper Input Validation) + CWE-436 (Interpretation Conflict). CWE-436 is the more precise classification for this parser differential / Zip Cloaking class of vulnerability. |
Sources.
- CVE-2026-115905 — Offseq Radar (full details)
- CVE-2026-115905 — Strix AI (CVSS 4.0 vector)
- zip-rs/zip2 Release v9.0.5 (fix release)
- zip crate — crates.io (download stats, dependents)
- CVE-2026-115905 — ThreatInt CVE Database
- Zip Cloaking Deep Dive — HOL Guard
- ZIP Filename Confusion research (Python bug tracker)
- zip-rs/zip2 GitHub Repository
Why this verdict
- Library, not product: This is a vulnerability in a Rust library, not a directly deployable product. Impact is entirely mediated by how downstream applications consume the
zipcrate. The CNA's CVSS 4.0 score of 10.0 rates the theoretical worst case assuming all axes at maximum; real-world severity depends on each consumer's attack surface, API usage, and extraction context. - Streaming API narrows exposure: Only
ZipStreamReader(the streaming/non-seekable API) is affected. The far more commonly usedZipArchiveAPI reads filenames from the central directory and is NOT vulnerable to this specific differential. This significantly reduces the fraction of the 8,505 dependent crates that are actually exploitable through this path. - File overwrite, not direct RCE: The immediate, direct impact is arbitrary file write via path traversal. Escalation to code execution requires the overwritten file to be subsequently executed or interpreted (shell profile, cron job, CI config, systemd unit). This is a realistic but not guaranteed escalation path, and it adds a step the attacker must reason about.
- Role multiplier: In low-value roles (CLI tools, desktop apps, developer utilities), this is a MEDIUM-severity file-overwrite bug requiring targeted exploitation with limited blast radius. In high-value roles — specifically CI/CD pipelines (build artifact extraction, test fixture unpacking via streaming APIs), supply chain tools (package installers, updaters that stream-extract downloads), and server-side upload processors — arbitrary file overwrite is effectively equivalent to RCE because overwriting
Makefile,.github/workflows/*.yml,build.rs, or deployment configs leads to code execution on next build or deploy cycle. Given the zip crate's dominance as the #1 compression library in the Rust ecosystem (8,505 dependents), conservatively >5% of installs serve CI/CD or server-side file-processing roles whereZipStreamReadermay be in the call path. This sets the severity floor at HIGH. - No exploitation headroom: Zero days since CVE publication, no known PoC, no in-the-wild exploitation, no KEV listing. The technique is simple to execute but has not yet been weaponized against this specific library. This keeps the assessment at the HIGH floor rather than pushing toward CRITICAL.
Why not higher?
CRITICAL would require either (a) the affected component to be canonically deployed in a high-value role where >=10% of installs occupy that role by definition — this is a general-purpose library, not a hypervisor, identity provider, or network edge appliance — or (b) demonstrated in-the-wild exploitation or a weaponized PoC achieving RCE. The vulnerability is in a library where impact is fully mediated by consumer code, only the streaming API subset is affected, the direct impact is file overwrite rather than code execution, and there is no known exploitation. The CNA's 10.0 assumes maximum blast radius across every axis simultaneously, which does not hold for a dependency whose exploitability varies by consumer.
Why not lower?
MEDIUM is too low given the zip crate's position as the #1 compression library in the Rust ecosystem with 305M+ downloads and 8,505 dependents. The Zip Cloaking technique specifically defeats security inspection — this is not a simple input validation bug but a deliberate security control bypass. In CI/CD and supply chain contexts where the streaming API IS used with untrusted input, the chain terminates in effective RCE via file overwrite of build/deploy configs. The deployment-role floor analysis confirms well above 1% of installs in high-value roles, setting an unbreakable HIGH floor.
Crowdsourced verification payload.
Run on any build host, CI runner, or developer workstation where Rust projects are compiled. Pass the project root as an argument: bash cve_2026_115905.sh /path/to/rust/project. Requires only read access to Cargo.lock — no elevated privileges needed. To scan multiple projects: find /src -name Cargo.lock -execdir bash /path/to/cve_2026_115905.sh . \;
#!/usr/bin/env bash
# CVE-2026-115905 -- zip-rs/zip2 Zip Cloaking detection
# Scans Cargo.lock for vulnerable zip crate versions (< 9.0.5)
# Exit 0 = PATCHED, Exit 1 = VULNERABLE, Exit 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
LOCKFILE="$PROJECT_DIR/Cargo.lock"
FIXED="9.0.5"
if [ ! -f "$LOCKFILE" ]; then
echo "UNKNOWN - Cargo.lock not found at $LOCKFILE"
exit 2
fi
# Parse zip crate version(s) from Cargo.lock (field separator = double quote)
VERSIONS=$(awk -F'"' '/^name = "zip"$/{getline; print $2}' "$LOCKFILE")
if [ -z "$VERSIONS" ]; then
echo "UNKNOWN - zip crate not present in $LOCKFILE"
exit 2
fi
VULN=0
for v in $VERSIONS; do
LOWEST=$(printf '%s\n%s\n' "$v" "$FIXED" | sort -V | head -n1)
if [ "$LOWEST" = "$v" ] && [ "$v" != "$FIXED" ]; then
echo "VULNERABLE - zip $v < $FIXED (CVE-2026-115905)"
VULN=1
else
echo "PATCHED - zip $v >= $FIXED"
fi
done
exit $VULN