Archive Extraction Security Has Two Halves, and Cargo's tar Crate Only Covered One
Source: rust
The Rust Security Response Team published an advisory on March 21st for CVE-2026-33056, a vulnerability in the tar crate used by Cargo to extract packages during builds. The flaw allows a malicious .crate archive to change permissions on arbitrary directories on the filesystem. Rust 1.94.1, released March 26th, ships the patched tar crate.
The immediate impact is narrow. The crates.io team deployed server-side upload validation on March 13th, audited every crate ever published, and confirmed none were exploiting the vulnerability. For users of the public registry, the risk was mitigated before the advisory went public. The more interesting question is why this category of bug keeps appearing, what the specific mechanism was here, and what the gap means for teams running alternate Cargo registries.
What the tar Format Makes Possible
Each entry in a tar archive is a 512-byte header containing the file path, permission bits, uid/gid, size, and a type flag. The permission bits occupy an 8-byte octal field, typically something like 0000755. For directories, these bits control readability, writeability, and traversal permissions.
The attack is not file-path traversal. No entry in a CVE-2026-33056-exploiting archive needs to contain ../../ in its name. The archive writes nothing outside the extraction root. What it does instead is exploit the deferred chmod pass.
When an archive is extracted, directories must remain writable throughout the process so that files can be placed inside them. A tar implementation cannot apply a directory’s final permissions immediately on extraction, because a later entry might need to create files inside that directory. The common pattern is to accumulate (path, mode) pairs during extraction and apply them in a second pass at the end, after all files are placed.
The tar crate before the fix applied this deferred pass without re-validating that the paths being chmod’d were still within the extraction root. A crafted archive could include a symlink entry pointing outside the destination directory, followed by a directory entry relative to that symlink. The file-creation phase would reject or not write the escaping entry, but the deferred chmod pass would follow the symlink and call set_permissions() on the target outside the root.
# Conceptual structure of a malicious archive
Entry 1 (symlink): "build-artifacts" -> "/home/user/.ssh"
Entry 2 (directory): "build-artifacts/keys" with mode 0777
# File creation: "build-artifacts/keys" stays inside extraction root (symlink followed)
# Deferred chmod: set_permissions(0777) applied to /home/user/.ssh/keys via symlink
In practice this means an attacker could set 0000 on ~/.cargo, ~/.ssh, or any directory reachable through a symlink chain, effectively locking a user out. Setting 0777 on ~/.ssh causes OpenSSH to silently reject authorized_keys because it considers world-writable directories unsafe. On CI pipelines where cargo build runs as a service account with broader filesystem access, the blast radius extends considerably.
The timing matters: the permission change fires during package extraction, before compilation begins, before build.rs runs, and before any security scanner would inspect the code. It is not visible to cargo audit, which operates on dependency version data in the RustSec advisory database and has no visibility into extraction-time behavior.
The Lineage of This Class of Bug
This vulnerability has a direct lineage in other package ecosystems, and the pattern is consistent enough to be instructive.
The Python tarfile module had a documented path traversal issue since at least CVE-2007-4559, where ../ in entry paths allowed archives to write files outside the extraction directory. Python did not ship a proper fix until Python 3.12, more than fifteen years later, via the tarfile.data_filter mechanism. The delay came partly because the behavior was framed as user responsibility in documentation rather than a library-level problem.
Zip Slip, catalogued by Snyk in 2018, documented path traversal via archive entry names across Java, .NET, Go, Python, and Ruby. It found vulnerable implementations in dozens of widely-used libraries, many as transitive dependencies of package managers. The structural cause was identical in every case: libraries validated paths for file writes but left other operations unguarded.
The node-tar series in 2021 is the closest parallel to what happened here. CVE-2021-32803 and CVE-2021-32804 covered symlink-based path traversal in npm’s tar implementation. CVE-2021-37701, CVE-2021-37712, and CVE-2021-37713 followed because each fix introduced a new interaction the previous one had not covered. Secure archive extraction requires a family of invariants that must hold simultaneously: paths for file creation must not escape the root, symlink targets must not point outside, and targets of metadata operations must resolve within bounds even after following any previously extracted symlinks.
CVE-2026-33056 is the chmod invariant failing while the file-creation invariant held. The archive passed the path traversal check because it never tried to write a file outside the root. The fix in Rust 1.94.1 extends the same boundary checks used for file creation to every call that resolves a path before invoking set_permissions(), so the containment guarantee covers both halves of extraction.
The Alternate Registry Gap
For crates.io users, the coordinated response was well-executed. The registry deployed server-side validation before the public advisory, audited the full corpus of published crates, and the patch was available on the day of disclosure. Deploying the mitigation before disclosure is a meaningful difference from how many ecosystem-wide vulnerabilities are handled.
The gap is for teams running alternate Cargo registries. The Cargo registry protocol is open, and there are self-hosted options, corporate mirrors, and commercial offerings like JFrog Artifactory and Cloudsmith. None of these automatically receive crates.io server-side mitigations. Each operator must independently decide whether to validate incoming .crate archives against this class of vulnerability.
The advisory is direct on this point: users of alternate registries should contact their vendor to verify whether equivalent upload validation has been deployed. Older versions of Cargo pointed at alternate registries that have not deployed mitigations remain exposed, because the client-side fix is only present in 1.94.1.
Upgrading to Rust 1.94.1 closes the extraction vulnerability on the client side regardless of registry source. For teams that cannot immediately upgrade, the immediate step is to audit recently added or updated packages in any alternate registry and verify the registry operator has deployed upload validation. The relevant change in Cargo.lock after upgrading is the tar dependency moving to the patched version.
The broader structural issue is that alternate registries often function as internal mirrors with minimal validation, relying on the trustworthiness of whoever holds publish credentials. The crates.io corpus audit worked because one team controlled the full set of published artifacts. An enterprise registry with packages from dozens of teams has no equivalent central vantage point, and there is no automated mechanism to propagate crates.io’s server-side validation rules downstream.
What This Means for Cargo’s Security Model
Cargo has several security controls that operate at different layers. Cargo.lock provides reproducible builds with checksums verified against the registry index. cargo audit checks dependency versions against the RustSec advisory database. cargo-vet, developed at Mozilla, allows organizations to delegate audit trust across teams for build script review.
All of these operate on code after packages are downloaded and before or during compilation. The extraction layer, where .crate archives are unpacked into ~/.cargo/registry/src/, has received comparatively less scrutiny. CVE-2026-33056 is a reminder that the extraction phase has a distinct attack surface from the compilation phase.
The build.rs sandboxing problem has been tracked in cargo issue #4956 since 2017. The argument for sandboxing build scripts is that they run arbitrary code at compile time with user privileges. The argument this vulnerability adds is that the attack surface exists before build scripts even run, in the extraction machinery beneath them.
The Rust project’s response here reflects a supply chain security posture that compares well against other ecosystems. The underlying bug class, though, has now appeared in npm, pip, RubyGems, and Cargo. A tar archive is a tar archive, and every ecosystem that processes untrusted archives faces the same family of invariants to maintain. The deferred chmod pass is easy to overlook because it sits below the part of extraction that most security reviews focus on.