May 25, 2026·6 min read·engineeringrustsecuritycargoregistries

Cargo's symlink advisory concerns the extracted source cache

CVE-2026-5223 lets a malicious third-party registry archive overwrite another crate's cached source. The advisory exposes a distinction between trusting a downloaded archive and isolating the filesystem effects of unpacking it.

A crate archive should populate its own source directory. Cargo's May 25 advisory reports that a crafted symlink could break that isolation and overwrite another crate's extracted source from the same registry. The vulnerability is CVE-2026-5223. The Rust Security Response Team rates its severity as medium for users of third-party registries and states that crates.io users are unaffected because that registry forbids symlinks in uploaded crates.

The cache is the important part of the story. Cargo reuses extracted dependency source in future builds, so extraction establishes state that later compiler invocations trust. A malicious archive does not need to change another crate's published version to alter the files a local build reads. It can attack the separation between neighboring cache entries.

Rust's ownership semantics are a strong reason I prefer it to C for new low-level code. This defect concerns an earlier stage of producing a Rust program. The compiler cannot establish that a dependency's source still corresponds to the intended package if another extraction operation has modified those files. Source provenance and memory safety answer different questions.

Cargo keeps archives and extracted sources separately

The Cargo 1.95 home-directory guide describes the download and source cache. Registry archives live under registry/cache as gzip-compressed .crate files. Source files unpack under registry/src, where rustc can find the Rust files needed for compilation. Cargo home defaults to .cargo beneath the user's home directory, with CARGO_HOME available to choose another location.

Those two representations serve different purposes. The archive is the downloaded object, while the extracted tree is the filesystem representation the compiler consumes. Cargo's guide notes that caching the entire home can store the same source twice, once compressed and once extracted. That duplication also makes it useful to distinguish which representation a security investigation has established as trustworthy.

The advisory says Cargo already had protections intended to confine extraction to the crate's own cache directory. The symlink defect bypassed that isolation enough to affect other crate directories belonging to the same registry. Its impact should not be restated as an unrestricted overwrite anywhere on the machine. The published claim concerns another crate's cache within that registry.

Checksums and extraction constrain different operations

The registry-index specification includes a SHA-256 checksum for each .crate file. A registry download endpoint supplies that file. The checksum identifies an archive's bytes, while extraction determines what filesystem changes those bytes cause.

Even a correctly identified archive can contain a malicious instruction to an extractor. This is an inference from the vulnerability's mechanism, not a claim that Cargo's checksum validation was itself bypassed. Matching an archive to its registry record cannot establish that its symlinks remain confined after extraction. The extractor needs an independent rule for the filesystem effects it permits.

The same separation explains why a lockfile is not a complete answer to this defect. A recorded dependency selection can remain unchanged while the extracted files for that selection change locally. The security property at issue is whether installing one package can modify the source of another. Version selection does not enforce that filesystem boundary by itself.

This matters to the interpretation of a successful build. A compiler invocation can use the expected package names and still consume files modified by another extraction. A build log that records dependency versions is useful evidence, but it does not directly describe every preceding cache mutation. The advisory gives operators a specific reason to investigate the origin of cached source.

Cache reuse gives this distinction a time dimension. The package whose extraction caused the modification and the package whose build later consumes it need not be the same dependency operation. An investigation therefore needs more than the list of crates compiled in the most recent failed or suspicious build. It needs to establish which earlier extraction operations populated the source directories that build reused. This follows from the advisory's description of persistent source reuse, without assuming that any particular installation has been compromised. A short-lived source cache reduces the history to investigate, while a long-lived one requires more evidence about prior inputs. Neither cache lifetime changes the extractor's obligation to keep each package confined to its own directory. The defect remains an isolation failure even if a subsequent clean build happens to remove its effects.

Third-party registry policy determines exposure

The alternate-registry documentation describes naming a registry and configuring its index in .cargo/config.toml. Dependencies can then select that registry by name in the manifest. Cargo supports Git-based and sparse indexes, with the sparse protocol fetching individual metadata files over HTTP.

Those index protocols do not describe whether archive uploads may contain symlinks. The advisory's exposure distinction depends on that content policy. crates.io's rejection prevented the malicious archive form from entering that registry. Another registry's policy needs its own evidence, even when its users invoke the same Cargo commands and consume the same index format.

An internal registry is therefore not automatically exposed or automatically protected merely because it is private. The relevant questions concern who can supply archives and whether the registry rejects the archive features involved. Access restrictions may narrow the set of possible publishers, while archive validation constrains what any permitted publisher can upload. They affect different parts of the attack path.

The advisory recommends auditing third-party registry contents for symlinks and configuring the registry to reject them where possible. That is a concrete interim measure for users who cannot take the announced Cargo update. It addresses the input form the advisory identifies, without requiring application source changes.

As of this disclosure, Rust 1.96.0 is scheduled for May 28. The advisory says its Cargo update will reject extraction of any symlink in crate tarballs, including archives from third-party registries. All Cargo versions shipped before that release are affected. This post describes that announced correction before its scheduled release date.

The response chooses a simpler archive contract. Rejecting symlinks avoids supporting their filesystem semantics inside this package format. The advisory notes that cargo package and cargo publish did not add symlinks in the first place, so the Rust team expects limited compatibility impact from rejecting them on extraction.

That compatibility fact matters. A package manager can narrow an accepted format more safely when its own normal publishing path does not create the rejected feature. Third-party archive producers still need to conform to the new reader behavior. The change establishes a consistent consumer restriction even when a registry's own upload validation differs.

I favor that explicit restriction here. The useful operation is distributing a crate's source, and the normal Cargo publisher already does so without preserving symlinks. Maintaining a complicated extraction capability would need a concrete ecosystem requirement to justify its added isolation burden. The announced response gives registry operators and clients the same simple rule about this archive feature.

Mirrors and source replacement have a separate contract

The source-replacement reference describes mirrors and vendored sources as replacements expected to contain exactly the original source. It explicitly distinguishes source replacement from patching dependencies or operating a private registry. A replacement may provide a subset, but it must not introduce different crates or different source for a supposedly equivalent package.

That distinction helps keep the response accurate. A mirror serving the original crates.io archives and an independent registry accepting its own archive uploads are different arrangements. Calling both an internal Cargo service loses the fact needed to apply the advisory's crates.io qualification. The service's role and content policy need to be identified before assigning exposure.

The same reference distinguishes local registries containing .crate files from directory sources containing unpacked source. Directory sources have per-file checksum metadata to guard against accidental modification. These are different representations with different integrity mechanisms. The advisory does not establish that every such source arrangement has the registry-cache extraction defect.

For cached third-party source that may already have been exposed, the important remaining question is its history. Updating extraction behavior prevents the announced input form from succeeding in future operations. It does not by itself provide evidence about files produced by earlier operations. A recovery decision needs a trusted origin for those files and a clear account of which representation the build will consume next.