Back to Blog
critical SEVERITY10 min read

CVE-2025-7783: Critical form-data Unsafe Randomness Vulnerability Fixed

A critical vulnerability (CVE-2025-7783) was discovered in the widely-used `form-data` npm package, where an unsafe random function was used to generate multipart boundary strings, making them predictable and potentially exploitable by attackers. The fix upgrades `form-data` to patched versions (2.5.4, 3.0.4, and 4.0.4) across all supported major releases. Developers using any version of `form-data` prior to these patches should upgrade immediately to protect their applications from boundary pre

O
By Orbis AppSec
β€’Published April 15, 2026β€’Reviewed June 3, 2026

Answer Summary

CVE-2025-7783 is an unsafe randomness vulnerability (CWE-330) in the Node.js `form-data` npm package where `Math.random()` was used to generate multipart boundary strings, making them predictable to attackers. The fix requires upgrading to patched versions (2.5.4, 3.0.4, or 4.0.4) which replace the weak PRNG with cryptographically secure alternatives like `crypto.randomBytes()`.

Vulnerability at a Glance

cweCWE-330 (Use of Insufficiently Random Values)
fixUpgrade form-data to 2.5.4, 3.0.4, or 4.0.4 with cryptographically secure randomness
riskRequest smuggling, content injection, authentication bypass
languageJavaScript (Node.js)
root causeMath.random() used for security-sensitive boundary string generation
vulnerabilityUnsafe Randomness / Predictable Boundary Generation

CVE-2025-7783: Critical form-data Unsafe Randomness Vulnerability Fixed

Severity: πŸ”΄ CRITICAL | Package: form-data | Fixed In: 2.5.4, 3.0.4, 4.0.4


Introduction

If your Node.js application submits forms, uploads files, or communicates with APIs using multipart data, there is a very good chance you depend on the form-data package β€” either directly or as a transitive dependency. With tens of millions of weekly downloads on npm, form-data is one of the most quietly essential libraries in the JavaScript ecosystem.

On the surface, it does something deceptively simple: it constructs multipart/form-data payloads, the same format your browser uses when you upload a file through an HTML form. But buried in that simplicity was a critical security flaw β€” the library was using an unsafe, non-cryptographic random function to generate the boundary strings that separate multipart fields.

This vulnerability, tracked as CVE-2025-7783, has been rated CRITICAL in severity. In this post, we'll break down exactly what went wrong, how it could be exploited, and what you need to do to protect your applications.


Background: What Is form-data?

The form-data npm package is a Node.js implementation of the multipart/form-data specification defined in RFC 7578. It is used to programmatically construct form submissions β€” particularly for file uploads β€” in environments where a browser is not present.

You'll find it as a direct or indirect dependency in packages like:
- axios
- node-fetch
- superagent
- request (deprecated but still widely used)
- Countless internal API clients and SDKs

A typical usage looks like this:

const FormData = require('form-data');
const fs = require('fs');

const form = new FormData();
form.append('username', 'alice');
form.append('avatar', fs.createReadStream('/path/to/avatar.png'));

// The resulting HTTP body looks something like:
// --abc123randomBoundary456xyz
// Content-Disposition: form-data; name="username"
//
// alice
// --abc123randomBoundary456xyz
// Content-Disposition: form-data; name="avatar"; filename="avatar.png"
// Content-Type: image/png
//
// <binary data>
// --abc123randomBoundary456xyz--

That abc123randomBoundary456xyz string is the boundary β€” a delimiter that tells the server where one field ends and the next begins. The security of multipart parsing depends heavily on this boundary being unpredictable.


The Vulnerability Explained

What Went Wrong: Predictable Boundaries

The core of CVE-2025-7783 is straightforward but serious: form-data was using Math.random() to generate multipart boundary strings instead of a cryptographically secure random number generator (CSPRNG).

Here's a simplified illustration of what the vulnerable code likely looked like:

// ❌ VULNERABLE: Using Math.random() β€” NOT cryptographically secure
function generateBoundary() {
  let boundary = '--------------------------';
  for (let i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(10);
  }
  return boundary;
}

And what a secure implementation should look like:

// βœ… SECURE: Using crypto.randomBytes() β€” cryptographically secure
const crypto = require('crypto');

function generateBoundary() {
  return `----FormBoundary${crypto.randomBytes(16).toString('hex')}`;
}

Why Does Math.random() Matter Here?

Math.random() is designed for statistical randomness β€” things like shuffling a deck of cards in a game or picking a random color for a UI element. It is explicitly not designed for security purposes.

Here's why it falls short:

Property Math.random() crypto.randomBytes()
Cryptographically secure ❌ No βœ… Yes
Seed is unpredictable ❌ No βœ… Yes
Output is predictable given state βœ… Yes (bad!) ❌ No
Suitable for security tokens ❌ No βœ… Yes

The V8 JavaScript engine (used by Node.js) implements Math.random() using an algorithm called xorshift128+. While it produces numbers that look random, a determined attacker who can observe a few outputs from Math.random() can reconstruct the internal state of the PRNG and predict all future outputs.

This is not theoretical β€” tools like Practical Predictability of Math.random() demonstrate exactly this attack.

Real-World Attack Scenarios

Scenario 1: Boundary Injection / Pollution

If an attacker can predict the boundary string before a request is sent, they may be able to craft malicious input data that contains the boundary string. When the server parses the multipart body, it would interpret the injected boundary as a real delimiter, potentially:

  • Splitting a single field into multiple fields
  • Injecting additional form fields the application never intended to send
  • Overwriting legitimate field values (e.g., injecting admin=true after a role=user field)
# Attacker knows the boundary will be: ----FormBoundary1234567890

# Malicious input in a "comment" field:
Hello World
----FormBoundary1234567890
Content-Disposition: form-data; name="role"

admin
----FormBoundary1234567890--

# Server parses this as TWO fields: "comment" AND "role=admin"

Scenario 2: Security Token Exposure via Timing Attacks

In applications where the boundary is logged, transmitted over a side channel, or otherwise observable, an attacker could use the predictability of Math.random() to correlate requests, de-anonymize users, or link together supposedly independent sessions.

Scenario 3: CSRF Token Bypass in Multipart Forms

Some CSRF protection schemes rely on the unpredictability of multipart boundaries as an additional layer of security (the "secret" is that only the legitimate origin can construct a valid multipart body). A predictable boundary undermines this assumption entirely.

Scenario 4: File Upload Manipulation

In systems that process file uploads, a predictable boundary could allow an attacker to manipulate the parsing of a file's binary content β€” particularly if the file itself is attacker-controlled β€” causing the server to misinterpret where the file data ends and metadata begins.


The Fix

What Changed

The maintainers of form-data released patched versions across all actively supported major version lines:

Major Version Vulnerable Patched
2.x < 2.5.4 2.5.4
3.x < 3.0.4 3.0.4
4.x < 4.0.4 4.0.4

The fix replaces the use of Math.random() with Node.js's built-in crypto module, specifically crypto.randomBytes(), which draws entropy from the operating system's CSPRNG (/dev/urandom on Linux/macOS, BCryptGenRandom on Windows).

Before vs. After

Before (Vulnerable):

// Using Math.random() β€” output is predictable
FormData.prototype._generateBoundary = function() {
  var boundary = '--------------------------';
  for (var i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(10);
  }
  this._boundary = boundary;
};

After (Secure):

// Using crypto.randomBytes() β€” output is cryptographically unpredictable
const crypto = require('crypto');

FormData.prototype._generateBoundary = function() {
  this._boundary = `----FormBoundary${crypto.randomBytes(16).toString('hex')}`;
};

With crypto.randomBytes(16), the boundary contains 128 bits of cryptographically secure entropy. An attacker would need to make 2¹²⁸ guesses on average to predict it β€” effectively impossible with current technology.

How This Was Applied in the PR

The fix was applied by updating webui/package.json and the corresponding webui/yarn.lock to pin form-data to the patched versions. Because form-data is often a transitive dependency (pulled in by other packages), the yarn.lock update ensures that all resolution paths β€” regardless of which parent package requested form-data β€” resolve to the secure version.

// webui/package.json (conceptual β€” ensuring patched versions)
{
  "resolutions": {
    "form-data": "^4.0.4"
  }
}

Using resolutions (in Yarn) or overrides (in npm 8+) is a powerful technique to force all transitive dependencies to use a specific patched version of a package, even when the direct dependency hasn't updated their own package.json yet.


Prevention & Best Practices

1. Never Use Math.random() for Security Purposes

This is the most important takeaway. Math.random() has exactly one appropriate use case in security contexts: none.

Use Case Correct Tool
Random tokens, IDs, boundaries crypto.randomBytes()
UUIDs crypto.randomUUID() (Node 14.17+)
Secure passwords crypto.randomBytes() + encoding
Random numbers in range (secure) crypto.randomInt() (Node 14.10+)
Shuffling for games/UI Math.random() βœ…
// βœ… Generating a secure random token in Node.js
const crypto = require('crypto');

// 32 bytes = 256 bits of entropy, hex encoded
const secureToken = crypto.randomBytes(32).toString('hex');

// Or as a UUID
const secureUUID = crypto.randomUUID();

// Or a secure random integer in range [0, 100)
const secureInt = crypto.randomInt(100);

2. Audit Your Dependencies Regularly

CVE-2025-7783 affects a package that most developers never directly interact with β€” it's a transitive dependency. This is why automated dependency scanning is non-negotiable.

Recommended tools:

# npm audit (built-in)
npm audit
npm audit fix

# Snyk
npx snyk test

# Trivy (used to detect this CVE)
trivy fs --scanners vuln .

# GitHub Dependabot
# Enable in .github/dependabot.yml

3. Use Lock Files and Dependency Pinning

Always commit your yarn.lock or package-lock.json. These files ensure that everyone on your team β€” and your CI/CD pipeline β€” installs the exact same versions of every dependency, including transitive ones.

When a vulnerability is found in a transitive dependency, use resolution overrides to force the patched version:

// package.json β€” Yarn resolutions
{
  "resolutions": {
    "form-data": ">=4.0.4"
  }
}
// package.json β€” npm overrides (npm 8+)
{
  "overrides": {
    "form-data": ">=4.0.4"
  }
}

4. Implement a Vulnerability Management Process

Don't wait for vulnerabilities to be reported to you β€” build the process into your development lifecycle:

  1. Shift Left: Run npm audit or equivalent in your CI pipeline on every pull request
  2. Monitor: Subscribe to security advisories for your key dependencies (GitHub Advisory Database, Snyk, OSS Index)
  3. Respond: Define an SLA for patching based on severity (e.g., Critical: 24-48 hours, High: 1 week)
  4. Automate: Use tools like Dependabot, Renovate, or OrbisAI Security to automatically open PRs for security fixes

5. Understand the OWASP & CWE Context

This vulnerability maps to well-known security weakness classifications:

Understanding these classifications helps you recognize the same class of vulnerability in other contexts β€” whether it's in session token generation, password reset links, API keys, or, as in this case, multipart boundaries.

6. Code Review Checklist for Randomness

When reviewing code that generates any kind of security-sensitive random value, ask:

  • [ ] Is this using Math.random(), Date.now(), or process PID as a source of randomness? 🚨
  • [ ] Is this value used in a security context (tokens, IDs, boundaries, nonces)?
  • [ ] Is the entropy sufficient (at least 128 bits for most security applications)?
  • [ ] Is the output space large enough to resist brute-force guessing?

Conclusion

CVE-2025-7783 is a textbook example of a subtle but critical security mistake: using the wrong tool for the job. Math.random() is a perfectly fine function for non-security use cases, but its use in generating multipart boundaries β€” values that need to be unpredictable to maintain the integrity of HTTP requests β€” introduced a critical vulnerability into one of npm's most downloaded packages.

The fix is straightforward (swap Math.random() for crypto.randomBytes()), but the lesson is broad: cryptographic security requires cryptographic tools. Non-cryptographic PRNGs have no place in security-sensitive code paths, and it's the responsibility of both library authors and application developers to know the difference.

Key Takeaways:

  1. βœ… Upgrade immediately: Update form-data to 2.5.4, 3.0.4, or 4.0.4 depending on your version
  2. βœ… Audit transitive dependencies: You are responsible for the security of every package in your node_modules, not just the ones you directly import
  3. βœ… Use CSPRNGs for security: crypto.randomBytes() and crypto.randomUUID() are your friends
  4. βœ… Automate vulnerability detection: Make dependency scanning a standard part of your CI/CD pipeline
  5. βœ… Learn the patterns: CWE-338 and similar weaknesses appear across all languages and frameworks β€” learn to recognize them

Security is a team sport. Keep your dependencies updated, keep scanning, and keep learning.


This fix was automatically detected and remediated by OrbisAI Security. Automated security tooling identified the vulnerable form-data version via Trivy scanning and opened a pull request with the appropriate patches β€” demonstrating how automation can dramatically reduce the window of exposure for known CVEs.


References:
- CVE-2025-7783 – NVD
- form-data on npm
- CWE-338: Use of Cryptographically Weak PRNG
- Node.js Crypto Documentation
- OWASP Top 10: A02 Cryptographic Failures
- RFC 7578: Returning Values from Forms: multipart/form-data

Frequently Asked Questions

What is unsafe randomness in multipart boundaries?

Unsafe randomness occurs when predictable random number generators like Math.random() are used to create boundary strings that separate parts in multipart HTTP requests, allowing attackers to guess these values and manipulate request parsing.

How do you prevent unsafe randomness vulnerabilities in Node.js?

Use cryptographically secure random number generators like crypto.randomBytes() or crypto.randomUUID() instead of Math.random() for any security-sensitive values including tokens, boundaries, session IDs, and nonces.

What CWE is unsafe randomness?

CWE-330 (Use of Insufficiently Random Values) covers vulnerabilities where weak random number generation is used in security contexts, making values predictable to attackers.

Is using Math.random() ever safe for security purposes?

No, Math.random() is never appropriate for security-sensitive operations. It uses a predictable PRNG algorithm that can be reverse-engineered, making any derived values guessable by attackers who observe enough outputs.

Can static analysis detect unsafe randomness vulnerabilities?

Yes, static analysis tools can flag uses of Math.random() in security-sensitive contexts. Tools like Semgrep, ESLint security plugins, and Orbis AppSec can identify when weak PRNGs are used for boundary generation, token creation, or cryptographic operations.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #12958

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versionsβ€”including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.