Back to Blog
critical SEVERITY7 min read

How Unsafe Random Functions Happen in Node.js Form Data and How to Fix It

CVE-2025-7783 is a critical vulnerability in the `form-data` npm package caused by the use of an unsafe random number generator to produce multipart form boundaries, making those boundaries predictable by an attacker. The fix upgrades `form-data` to versions 2.5.4, 3.0.4, and 4.0.4, which replace the weak random function with a cryptographically secure alternative. This change was applied to the `example-apps/collector/package-lock.json` and `package.json` files in the Instana collector example

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2025-7783 is a critical vulnerability (CWE-338) in the `form-data` npm package for Node.js where a non-cryptographic random function was used to generate multipart form boundaries, making them predictable to attackers. A predictable boundary enables boundary injection attacks that can manipulate multipart request parsing. The fix is to upgrade `form-data` to version 2.5.4, 3.0.4, or 4.0.4, which replace the unsafe `Math.random()`-based boundary generator with a cryptographically secure one. In this repository, the upgrade was applied by updating `example-apps/collector/package-lock.json` and `package.json`.

Vulnerability at a Glance

cweCWE-338
fixUpgrade `form-data` to 2.5.4, 3.0.4, or 4.0.4, which use a CSPRNG for boundary generation
riskAttackers can predict multipart form boundaries, enabling boundary injection and potential request smuggling or content manipulation
languageJavaScript / Node.js
root cause`form-data` used `Math.random()` instead of a cryptographically secure RNG to generate multipart boundaries
vulnerabilityUnsafe Pseudorandom Number Generator (PRNG) for Security-Sensitive Value

How Unsafe Random Functions Happen in Node.js Form Data and How to Fix It

Introduction

The example-apps/collector/package-lock.json file locks the dependencies for an Instana collector example application — a component that instruments Node.js services and sends telemetry data. Buried inside its dependency tree was a quietly dangerous flaw: the form-data package, pinned at version 2.3.3, was generating multipart form boundaries using Math.random(), a function that was never designed to produce unpredictable values in a security context.

This is CVE-2025-7783, rated critical, and it affects every application that uses a vulnerable version of form-data to send HTTP multipart requests — which includes a very large portion of the Node.js ecosystem, since form-data is a transitive dependency of popular packages like axios, node-fetch, and many others.


The Vulnerability Explained

What Is a Multipart Boundary?

When a browser or HTTP client sends a multipart/form-data request (used for file uploads and complex form submissions), it separates each field using a boundary string — a unique delimiter that must not appear inside any of the field values. A typical multipart request looks like this:

Content-Type: multipart/form-data; boundary=----FormBoundary7MA4YWxkTrZu0gW

------FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="upload.txt"

<file contents here>
------FormBoundary7MA4YWxkTrZu0gW--

The boundary is announced in the Content-Type header and used by the server to parse the body. If an attacker can predict or control the boundary string, they can inject their own boundary into field values and manipulate how the server parses the request.

The Root Cause: Math.random() for a Security-Sensitive Value

In the vulnerable versions of form-data (including 2.3.3 locked in this repository), the boundary was generated using JavaScript's Math.random(). Here is the conceptual pattern that was in use:

// VULNERABLE — from form-data before the fix
function generateBoundary() {
  var boundary = '--------------------------';
  for (var i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(16);
  }
  return boundary;
}

Math.random() is a pseudo-random number generator (PRNG). It is seeded from a predictable internal state and is explicitly documented by every JavaScript engine as not suitable for cryptographic or security-sensitive use. An attacker with knowledge of the timing or environment of a Node.js process can potentially predict or brute-force the sequence of values produced by Math.random().

This maps directly to CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).

How This Can Be Exploited

Consider the Instana collector application in this repository. It uses form-data to send multipart HTTP requests carrying telemetry, traces, or profiling data. An attack scenario looks like this:

  1. Attacker observes timing: The attacker knows (or can estimate) when the Node.js process started, which seeds Math.random().
  2. Attacker predicts the boundary: By modeling the PRNG state, the attacker predicts the boundary string that will be used for an upcoming multipart request.
  3. Attacker injects boundary into a field value: If any part of the uploaded data is attacker-influenced (e.g., a user-supplied filename, a log entry, or a trace attribute), the attacker embeds the predicted boundary string inside that value.
  4. Server misparses the request: The server's multipart parser sees the injected boundary as a legitimate field separator, splitting or corrupting the parsed data — potentially injecting a new field, truncating a file, or bypassing content validation.

In the context of an observability agent like Instana, this could mean corrupting trace data, injecting false telemetry, or bypassing security controls that rely on inspecting uploaded content.


The Fix

What Changed

The fix upgrades form-data from 2.3.3 to the patched versions 2.5.4, 3.0.4, and 4.0.4. The patched versions replace the Math.random()-based boundary generator with Node.js's built-in crypto.randomBytes(), which produces cryptographically secure random values.

The secure pattern used in the fixed versions looks like this:

// FIXED — form-data 2.5.4 / 3.0.4 / 4.0.4
var crypto = require('crypto');

function generateBoundary() {
  return crypto.randomBytes(16).toString('hex');
}

crypto.randomBytes() draws entropy from the operating system's CSPRNG (e.g., /dev/urandom on Linux), making the output statistically indistinguishable from random and computationally infeasible to predict.

Before and After: package-lock.json

The package-lock.json change in example-apps/collector/ reflects the version bump. Here is the relevant portion of the diff:

- "@instana/collector": {
-   "version": "1.119.1",
-   ...
-   "dependencies": {
-     "@instana/core": "1.119.1",
-     ...
-   }
- }
+ "@instana/collector": {
+   "version": "6.5.0",
+   ...
+   "license": "MIT",
+   "dependencies": {
+     "@instana/core": "6.5.0",
+     ...
+   }
+ }

The @instana/collector package was also upgraded from 1.119.1 to 6.5.0 as part of this change, which brings in the patched form-data transitively. The @instana/autoprofile package similarly jumped from 1.119.1 to 6.5.0, updating its engine requirement from node >=6.4.0 to node >=18.19.0, reflecting the broader modernization of the dependency tree alongside the security fix.

Why Both package.json and package-lock.json Were Updated

  • package.json: Declares the direct dependency version constraint. Updating this ensures that future npm install runs will resolve to the patched version rather than silently re-installing the vulnerable one.
  • package-lock.json: Locks the exact resolved version and its full sub-dependency tree. Without updating the lockfile, a npm ci command (used in CI/CD pipelines) would continue installing the vulnerable 2.3.3 version regardless of what package.json says.

Both files must be updated together for the fix to be durable.


Prevention & Best Practices

1. Never Use Math.random() for Security-Sensitive Values

Math.random() is appropriate for games, UI animations, and non-security shuffles. It is never appropriate for:
- Tokens, session IDs, or nonces
- Cryptographic keys or salts
- Boundary strings in security-sensitive multipart requests
- Any value whose unpredictability is a security requirement

Use instead: crypto.randomBytes(n) (Node.js), crypto.getRandomValues() (browser), or a well-audited library like uuid v4 (which uses CSPRNG internally).

2. Audit Your Transitive Dependencies

form-data is rarely a direct dependency — it is pulled in by axios, superagent, node-fetch, and many other popular packages. Use npm ls form-data to find all versions resolved in your dependency tree:

npm ls form-data

If you see 2.3.3 or any version below 2.5.4 / 3.0.4 / 4.0.4, you are vulnerable.

3. Use Automated Dependency Scanning

Tools that can detect this class of vulnerability:

  • Trivy (flagged this exact issue in the PR)
  • npm audit / yarn audit
  • Dependabot / Renovate for automated PRs
  • Semgrep with the javascript.lang.security.audit.math-random rule

4. Keep Lockfiles in Version Control

Always commit package-lock.json or yarn.lock. Without a lockfile, npm install may resolve a different (potentially vulnerable) version than what was tested.

5. Reference Security Standards


Key Takeaways

  • Math.random() in form-data produced predictable multipart boundaries — a value that must be unpredictable to prevent boundary injection attacks.
  • The fix is a version upgrade to form-data 2.5.4, 3.0.4, or 4.0.4, which internally switches boundary generation to crypto.randomBytes().
  • Transitive dependencies are a real attack surface: form-data was not a direct dependency of this application — it came in through @instana/collector. Scanning the full dependency tree (not just direct dependencies) is essential.
  • Both package.json and package-lock.json must be updated to make a dependency fix durable across all install modes, including npm ci in CI/CD pipelines.
  • Trivy's static analysis of the lockfile was sufficient to detect this — you do not need runtime instrumentation to catch known-vulnerable dependency versions.

How Orbis AppSec Detected This

  • Source: The form-data package, resolved as a transitive dependency in example-apps/collector/package-lock.json, generates a multipart boundary string that is included in every outbound HTTP multipart request.
  • Sink: The generateBoundary() function inside form-data (versions < 2.5.4 / 3.0.4 / 4.0.4) calls Math.random() to produce a security-sensitive delimiter value.
  • Missing control: No cryptographically secure entropy source was used; Math.random() provides no security guarantees and its output is predictable given knowledge of the PRNG state.
  • CWE: CWE-338 — Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
  • Fix: form-data was upgraded to 2.5.4 / 3.0.4 / 4.0.4, which replaces Math.random() with crypto.randomBytes() for boundary generation.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2025-7783 is a reminder that cryptographic correctness is not just about encryption algorithms — it extends to every value in your application whose unpredictability has security implications. A multipart form boundary sounds innocuous, but when it is generated with Math.random(), it becomes a predictable string that an attacker can weaponize to manipulate request parsing.

The fix is straightforward: upgrade form-data to 2.5.4, 3.0.4, or 4.0.4. But the broader lesson is to treat any randomly generated value in a security context with the same rigor you would apply to a password or a token. Use crypto.randomBytes(), audit your transitive dependencies regularly, and keep your lockfiles up to date.


References

Frequently Asked Questions

What is CVE-2025-7783 in form-data?

It is a critical vulnerability where the form-data npm package used a non-cryptographic random function (Math.random()) to generate multipart form boundaries, making those boundaries predictable to attackers.

How do you prevent unsafe PRNG usage in Node.js?

Use Node.js's built-in `crypto.randomBytes()` or `crypto.randomUUID()` for any value that must be unpredictable, such as tokens, nonces, or form boundaries. Never use `Math.random()` for security-sensitive generation.

What CWE is the unsafe random function vulnerability?

CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).

Is pinning the dependency version enough to prevent this vulnerability?

Pinning alone is not enough if the pinned version is the vulnerable one. You must upgrade to a patched version (2.5.4, 3.0.4, or 4.0.4) and lock that version in your package-lock.json.

Can static analysis detect unsafe PRNG usage?

Yes. Tools like Trivy (which flagged this issue), Semgrep, and ESLint security plugins can detect uses of Math.random() in security-sensitive contexts.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2682

Related Articles

critical

How Plaintext Token Storage happens in TypeScript/Tauri and how to fix it

A critical vulnerability in a Tauri desktop application allowed GitHub API tokens with full `repo` scope to be written to plaintext local storage files via the `getAllSettings()` function in `src/config/settings.ts`. Any process with filesystem access — including malware, other apps, or a logged-in attacker — could silently extract these tokens. The fix introduces a `SENSITIVE_KEYS` exclusion set that prevents credentials from being serialized to disk.

critical

How Weak Randomness Happens in Node.js WS-Security and How to Fix It

A critical vulnerability in `src/security/WSSecurity.ts` used `Math.random()` to generate nonces for WS-Security UsernameToken authentication, making nonces statistically predictable and defeating replay protection. By replacing the insecure SHA1-hashed random value with `crypto.randomBytes(16)`, the fix ensures nonces are cryptographically unpredictable. This change protects all downstream consumers of this Node.js SOAP library from nonce-prediction attacks on WS-Security authenticated endpoint

critical

How Implicit TLS Certificate Verification Happens in Python and How to Fix It

A critical security vulnerability was discovered in `plugins/python-build/scripts/add_cpython.py` where `requests.get()` calls to the GitHub API and OpenSSL release endpoints lacked explicit TLS certificate verification enforcement and consistent error handling. While Python's `requests` library defaults to `verify=True`, the absence of explicit enforcement and centralized error handling left the build tool exposed to man-in-the-middle attacks that could inject malicious package data. The fix in

critical

How Unauthenticated API Endpoints happen in Node.js Express and how to fix it

The `/token` endpoint in `plugin/multiplex/index.js` generated presentation control tokens without verifying the requester's identity, allowing any attacker with network access to seize control of a live reveal.js presentation. The fix restricts token generation to localhost-only requests and replaces a broken cryptographic primitive with a proper SHA-256 hash. Together, these changes eliminate both the access-control gap and a secondary cryptographic weakness in a single targeted patch.

critical

How Unsafe Random Functions Happen in Node.js form-data and How to Fix It

CVE-2025-7783 is a critical vulnerability in the `form-data` npm package caused by its use of an unsafe random function to generate multipart form boundaries. This flaw allows attackers to predict boundary values, potentially enabling them to manipulate or inject content into multipart requests. The fix upgrades `form-data` to version 4.0.6 and enforces this version across the entire dependency tree using a `package.json` `overrides` directive.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript