The Rust Security Response Team published an advisory on March 21st detailing CVE-2026-33056, a vulnerability in the tar crate that Cargo uses to unpack .crate files during builds. The premise is unsettling once you sit with it: a crate pulled from a registry can change the permissions on arbitrary directories on your filesystem without writing a single file outside the extraction root. No shellcode, no injected binary, just a crafted tar header and a code path that doesn’t validate what it chmodded.
The good news is that crates.io deployed server-side mitigations on March 13th before the public disclosure, audited the entire publication history, and found no evidence of exploitation in the wild. Rust 1.94.1, released March 26th, ships a patched version of the tar crate. The less comfortable news is that alternate registry users have a more complex situation, and the vulnerability class itself deserves a closer look.
Why tar extraction has a second pass
When Cargo downloads a package, it receives a .crate file, which is a gzipped tar archive. Cargo hands this off to the tar crate, which reads each entry and writes the corresponding file or directory to disk along with the metadata stored in the entry’s header: ownership, timestamps, and Unix permissions encoded as a mode field.
Files are straightforward. Write the content, apply the mode, done. Directories complicate this because the extractor needs them to be writable during extraction to place files inside them. If a directory’s intended final mode is 0555 (read and execute only), writing it that way up front blocks extraction of its contents. So many tar implementations, including the Rust tar crate, defer directory permissions: extract everything first, accumulate a list of directories and their intended modes, then apply those modes in a second pass at the end.
This deferred pass is where CVE-2026-33056 lives. If the implementation doesn’t re-validate that each path in the deferred list still resolves within the extraction root, the second pass becomes a mechanism for calling set_permissions() on paths the attacker specified. An archive can contain directory entries with ../ traversal sequences in their paths. The extraction phase may sanitize or reject those paths for file writes, but if the permission-setting pass operates on the raw stored path rather than a canonicalized, bounds-checked one, it will faithfully chmod whatever the archive told it to.
The result is an arbitrary chmod primitive executed under the privileges of whoever ran cargo build.
What permission manipulation enables
Arbitrary permission changes are less visceral than arbitrary file writes, but they enable a meaningful range of follow-on conditions depending on the target.
Setting a directory to mode 000 causes denial of service. A user’s home directory, /tmp, /var/run, or any directory critical to a running service can be locked against all access. On CI/CD pipelines where cargo build runs as a build user with broad filesystem access, the scope of potential disruption expands considerably.
Setting a directory to mode 777 weakens security assumptions that other tools rely on. OpenSSH, for example, refuses to use authorized_keys files if the .ssh directory has world-writable permissions; chmod-ing ~/.ssh to 777 can silently break key-based authentication for the user. The same pattern applies to directories used by sudo policy files, PAM modules, and cron, all of which validate permissions on sensitive paths before trusting their contents.
Applying setuid or setgid bits to directories the attacker can also write to can seed privilege escalation chains, though this path typically requires additional conditions on the target system.
None of these require the malicious crate to include any source code that compiles and runs. The attack executes entirely within the extraction phase, before Cargo has compiled anything.
A recurring pattern across package ecosystems
This vulnerability class has surfaced repeatedly in package managers across languages, usually via different mechanisms but with the same structural root: archive extraction requires reasoning about paths in two namespaces simultaneously, the path as stored in the archive header (attacker-controlled) and the path as it resolves on the local filesystem (what the extractor actually touches). Every time implementation fails to reconcile these, there is an opening.
Zip Slip, documented by Snyk in 2018, showed that path traversal during extraction was widespread across Java, .NET, Ruby, Go, and Python. The attack used ../../ in zip or tar entry paths to write files outside the destination directory. The research identified vulnerable implementations in dozens of widely-used libraries, many of which were transitive dependencies of package managers and build tools.
The npm tar package accumulated multiple extraction vulnerabilities in 2021, tracked as CVE-2021-32803, CVE-2021-32804, and CVE-2021-37713. Several involved symlink-based path escape: a symlink planted by an earlier archive entry would cause subsequent entries to be written through the symlink to a location outside the extraction root. npm’s response included multiple point releases and a significant hardening of the path validation logic.
Python’s tarfile module had a path traversal flaw that persisted for over fifteen years before Python 3.12 introduced the filter= parameter as a mitigation. The default behavior of tarfile.extractall() without filters allowed archives to write files to ../ paths. The longevity of that issue is partly because the behavior was documented and partly because it was framed as a caller responsibility rather than a library defect, until the security community pushed hard enough to change the default.
RubyGems collected a cluster of extraction-related CVEs in 2019, including CVE-2019-8320 through CVE-2019-8324, where symlink attacks during gem installation could overwrite arbitrary files with the privileges of the installing user.
The Rust tar crate’s vulnerability differs from most of these in that it doesn’t extract files outside the root; it modifies permissions outside the root. The path traversal is confined to the deferred chmod pass. But the structural cause is identical: a code path that acts on stored archive metadata without verifying that the resolved path is still within bounds.
The response and what it means for alternate registries
The Rust team followed a coordinated disclosure pattern. Server-side mitigations went live on crates.io on March 13th, blocking uploads of crates that would exploit the vulnerability. A full audit of historical crate publications confirmed no existing crate was exploiting CVE-2026-33056. The public advisory followed on March 21st, giving users time to act before the details were widely known. Rust 1.94.1 on March 26th completed the picture by shipping a patched tar crate in Cargo itself.
For users pulling exclusively from crates.io, the combination of the registry-level block and the toolchain update closes the issue at both ends. Updating to Rust 1.94.1 or later is sufficient.
Alternate registries are the more complicated case, and the advisory calls this out explicitly. Private registries, corporate mirrors, and third-party hosting don’t receive the server-side mitigations crates.io deployed. A registry that hasn’t applied equivalent filtering could be serving crates that exploit this, whether from malicious upload or from someone probing the vulnerability before the disclosure date. The recommendation to contact registry vendors is the right call, but it puts the burden on operators who may not have the capacity to audit their publication history the way the crates.io team did. Updating Cargo to 1.94.1 handles the client side regardless of registry, but older toolchain versions remain vulnerable to a hostile alternate registry.
The trust boundary that extraction occupies
Most security thinking about package managers focuses on what code runs after compilation: supply chain attacks that inject malicious logic into dependency source, backdoors in transitive dependencies, typosquatting attacks that get installed instead of a legitimate package. The extraction step is easy to treat as a mechanical precondition that happens before the real risk begins.
CVE-2026-33056 is a reminder that extraction is itself a trust boundary. The attacker doesn’t need code to reach a binary. A crafted tar archive is sufficient, and the window of exposure is anyone who runs cargo build or cargo install with an affected Cargo version pointing at a registry that hasn’t been audited.
The fix is conceptually straightforward: the deferred permission-setting pass needs to canonicalize paths and verify they resolve within the extraction root before calling set_permissions(). That is the same validation that should be applied to every other path operation during extraction, and the fact that it was missing in the chmod pass reflects how easy it is to treat bookkeeping steps as exempt from the same security discipline applied to the main extraction logic.
Full credit to Sergei Zimmerman for finding the underlying tar crate vulnerability and reporting it responsibly, to William Woodruff for assisting the crates.io team with mitigations, and to the Rust project members who coordinated the response and got a patched release out before the advisory went public. The coordination was clean. The class of bug will keep appearing until archive extraction libraries treat every path operation, deferred or not, as a potential attack surface.