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 Hardcoded Encryption Salts Compromise Credential Storage in Node.js and How to Fix It

A critical vulnerability in `scripts/bench-cpu.js` used a hardcoded static salt (`'byok-relay-salt'`) when deriving encryption keys with scrypt, allowing attackers to decrypt all encrypted credentials if the encryption secret was compromised. The fix replaces the hardcoded salt with cryptographically secure random bytes generated per operation, ensuring each user's encrypted credentials require a unique derived key.

high

How weak scrypt password hashing happens in Node.js and how to fix it

The `hashPass` function in `store-saas/server.mjs` used Node.js's `crypto.scryptSync` with default cost parameters (N=16384, r=8, p=1), making stored password hashes cheap to attack with modern GPUs. The fix increases the CPU/memory cost factor to N=131072 and parallelization to p=2, dramatically raising the computational effort required to brute-force stolen hashes.

high

How Dependency Version Pinning Prevents Supply Chain Attacks in Node.js and How to Fix It

A critical supply chain vulnerability in `package.json` allowed automatic updates to a cryptographic library with known weaknesses. By pinning `rijndael-js` to version `2.0.0` instead of allowing `^2.0.0` updates, the fix prevents silent installation of vulnerable versions that could expose downstream consumers to weak block cipher modes and authentication bypasses.

critical

How Insecure Randomness in form-data happens in Node.js and how to fix it

The `form-data` npm package, pinned at `^2.3.3` in `server/package-lock.json`, generated multipart form boundaries using the insecure `Math.random()` function instead of a cryptographically secure random source. This predictable boundary generation (CVE-2025-7783) could allow an attacker to guess or influence multipart boundaries, opening the door to request smuggling and payload injection in HTTP requests built by the server.

high

How Interpretation Conflict Vulnerability happens in Node.js and how to fix it

node-forge versions up to 1.3.1 shipped an ASN.1 parser vulnerable to an interpretation conflict that could let attackers bypass cryptographic signature verification, alongside a related unbounded recursion flaw (CVE-2025-66031) that enables denial-of-service. Upgrading the dependency to node-forge 1.4.0 patches both issues by hardening the ASN.1 decoder against malformed and adversarially crafted input.

high

How Man-in-the-Middle via ignored TLS options happens in Node.js undici SOCKS5 proxies and how to fix it

`dsh-coding-subscription-oauth` shipped `undici@7.24.8`, a release affected by CVE-2026-9697: when requests are routed through a SOCKS5 proxy, undici silently drops the caller-supplied TLS `connect` options (`ca`, `rejectUnauthorized`, `checkServerIdentity`, `servername`), so certificate pinning and custom trust stores are never applied. The fix pins `undici` to `7.29.0` across the app, `dsh-coding-oauth-core@0.1.1`, and both the production and development dispatchers, and hardens the Docker `de