CVE-2025-22150
Description
Undici is an HTTP/1.1 client. Starting in version 4.5.0 and prior to versions 5.28.5, 6.21.1, and 7.2.3, undici uses Math.random() to choose the boundary for a multipart/form-data request. It is known that the output of Math.random() can be predicted if several of its generated values are known. If there is a mechanism in an app that sends multipart requests to an attacker-controlled website, they can use this to leak the necessary values. Therefore, an attacker can tamper with the requests going to the backend APIs if certain conditions are met. This is fixed in versions 5.28.5, 6.21.1, and 7.2.3. As a workaround, do not issue multipart requests to attacker controlled servers.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
undicinpm | >= 4.5.0, < 5.28.5 | 5.28.5 |
undicinpm | >= 6.0.0, < 6.21.1 | 6.21.1 |
undicinpm | >= 7.0.0, < 7.2.3 | 7.2.3 |
Patches
66139ed2e0c78e260e7bb173a83537aa550281 file changed · +9 −1
lib/fetch/body.js+9 −1 modified@@ -22,6 +22,14 @@ const { isUint8Array, isArrayBuffer } = require('util/types') const { File: UndiciFile } = require('./file') const { parseMIMEType, serializeAMimeType } = require('./dataURL') +let random +try { + const crypto = require('node:crypto') + random = (max) => crypto.randomInt(0, max) +} catch { + random = (max) => Math.floor(Math.random(max)) +} + let ReadableStream = globalThis.ReadableStream /** @type {globalThis['File']} */ @@ -107,7 +115,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) } else if (util.isFormDataLike(object)) { - const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
c2d78cd19fe4Merge commit from fork
1 file changed · +9 −1
lib/web/fetch/body.js+9 −1 modified@@ -17,6 +17,14 @@ const { isErrored, isDisturbed } = require('node:stream') const { isArrayBuffer } = require('node:util/types') const { serializeAMimeType } = require('./data-url') const { multipartFormDataParser } = require('./formdata-parser') +let random + +try { + const crypto = require('node:crypto') + random = (max) => crypto.randomInt(0, max) +} catch { + random = (max) => Math.floor(Math.random(max)) +} const textEncoder = new TextEncoder() function noop () {} @@ -110,7 +118,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) } else if (webidl.is.FormData(object)) { - const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
c3acc6050b78Merge commit from fork
1 file changed · +9 −1
lib/web/fetch/body.js+9 −1 modified@@ -20,6 +20,14 @@ const { isErrored, isDisturbed } = require('node:stream') const { isArrayBuffer } = require('node:util/types') const { serializeAMimeType } = require('./data-url') const { multipartFormDataParser } = require('./formdata-parser') +let random + +try { + const crypto = require('node:crypto') + random = (max) => crypto.randomInt(0, max) +} catch { + random = (max) => Math.floor(Math.random(max)) +} const textEncoder = new TextEncoder() function noop () {} @@ -113,7 +121,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) } else if (util.isFormDataLike(object)) { - const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-c76h-2ccp-4975ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-22150ghsaADVISORY
- blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7fnvdWEB
- github.com/nodejs/undici/blob/8b06b8250907d92fead664b3368f1d2aa27c1f35/lib/web/fetch/body.jsnvdWEB
- github.com/nodejs/undici/commit/711e20772764c29f6622ddc937c63b6eefdf07d0nvdWEB
- github.com/nodejs/undici/commit/c2d78cd19fe4f4c621424491e26ce299e65e934anvdWEB
- github.com/nodejs/undici/commit/c3acc6050b781b827d80c86cbbab34f14458d385nvdWEB
- github.com/nodejs/undici/security/advisories/GHSA-c76h-2ccp-4975nvdWEB
- hackerone.com/reports/2913312nvdWEB
News mentions
0No linked articles in our index yet.