Back to Blog
critical SEVERITY6 min read

How unsafe random function in form-data happens in Node.js and how to fix it

The `form-data` npm package used `Math.random()` — a cryptographically unsafe pseudo-random number generator — to generate multipart form boundaries. This critical vulnerability (CVE-2025-7783) allowed attackers to predict boundary strings and potentially inject malicious content into multipart requests. The fix upgrades `form-data` from version 2.3.3 to 4.0.4, which uses a cryptographically secure random source.

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

Answer Summary

CVE-2025-7783 is a critical vulnerability in the Node.js `form-data` package (CWE-330) where `Math.random()` was used to generate multipart form boundary strings, making them predictable. The fix is to upgrade `form-data` to version 2.5.4, 3.0.4, or 4.0.4, which replace the unsafe random function with a cryptographically secure alternative like `crypto.randomBytes()`.

Vulnerability at a Glance

cweCWE-330 (Use of Insufficiently Random Values)
fixUpgrade form-data to 4.0.4 which uses cryptographically secure randomness
riskPredictable multipart boundaries enabling request smuggling or content injection
languageJavaScript (Node.js)
root causeform-data used Math.random() instead of crypto.randomBytes() for boundary generation
vulnerabilityUnsafe random function in multipart boundary generation

How Unsafe Random Function in form-data Happens in Node.js and How to Fix It

Introduction

In the agario-bots-proxies project, we discovered a critical vulnerability stemming from an outdated version of the form-data npm package (version 2.3.3) locked in ExampleScripts/agario-bots2/agario-bots-proxies/pnpm-lock.yaml. The package used Math.random() — JavaScript's built-in but cryptographically insecure pseudo-random number generator — to create multipart form boundary strings.

This matters because form-data is one of the most widely depended-upon packages in the Node.js ecosystem. It's pulled in transitively by request, request-promise, and countless HTTP client libraries. In this project, the dependency chain flows through request@2.88.0form-data@2.3.3, meaning every HTTP request that included multipart form data was using predictable boundaries.

The Vulnerability Explained

When you send a multipart form request (e.g., file uploads), the HTTP body uses a boundary string to separate different form fields. This boundary must be unique and unpredictable — if an attacker can guess it, they can craft payloads that break out of one form field and inject content into another.

In form-data versions prior to 2.5.4/3.0.4/4.0.4, the boundary was generated using code like:

FormData.prototype._generateBoundary = function() {
  // This uses Math.random() which is NOT cryptographically secure
  var boundary = '--------------------------';
  for (var i = 0; i < 24; i++) {
    boundary += Math.floor(Math.random() * 10).toString(16);
  }
  this._boundary = boundary;
};

Math.random() in V8 (Node.js's JavaScript engine) uses the xorshift128+ algorithm. This PRNG has well-documented properties:

  1. It is seeded with limited entropy — the internal state can be reconstructed from a small number of observed outputs.
  2. It is deterministic — once the state is known, all future outputs are predictable.
  3. State recovery is practical — researchers have demonstrated recovering xorshift128+ state from as few as 3-4 observed Math.random() outputs.

Attack Scenario

Consider this project's architecture: it's a bot proxy system for agar.io that makes HTTP requests through proxies. Here's how an attacker could exploit predictable boundaries:

  1. Observe: An attacker monitoring network traffic (or operating a malicious proxy in the socks-proxy-agent chain) captures a few multipart requests and extracts their boundary strings.
  2. Predict: Using the observed boundaries, the attacker recovers the Math.random() PRNG state and predicts future boundary values.
  3. Inject: The attacker crafts a response or man-in-the-middle payload containing the predicted boundary, allowing them to inject additional form fields or manipulate the multipart structure.
  4. Exploit: Injected content could include malicious file data, modified parameters, or crafted payloads that the receiving server processes as legitimate form fields.

In this specific project, where requests flow through multiple proxy layers (https-proxy-agent, socks-proxy-agent), the attack surface is amplified — any compromised proxy in the chain could observe and exploit the predictable boundaries.

The Fix

The fix upgrades form-data from the vulnerable version 2.3.3 to version 4.0.4, which uses crypto.randomBytes() for boundary generation.

Before (package.json):

{
    "dependencies": {
        "colour": "^0.7.1",
        "express": "^4.17.1",
        "https-proxy-agent": "^2.2.2",
        "murmurhash-js": "^1.0.0",
        "request": "^2.88.0",
        "request-promise": "^4.2.4",
        "ws": "^7.1.2",
        "socks-proxy-agent": "^4.0.2"
    }
}

After (package.json):

{
    "dependencies": {
        "colour": "^0.7.1",
        "express": "^4.17.1",
        "form-data": "4.0.4",
        "https-proxy-agent": "^2.2.2",
        "murmurhash-js": "^1.0.0",
        "request": "^2.88.0",
        "request-promise": "^4.2.4",
        "socks-proxy-agent": "^4.0.2",
        "ws": "^7.1.2"
    }
}

Two critical changes were made:

  1. package.json — Added "form-data": "4.0.4" as a direct, pinned dependency. This ensures the fixed version is resolved regardless of what transitive dependencies (like request) might pull in. By pinning to 4.0.4 exactly (no caret ^), the project guarantees this specific patched version.

  2. pnpm-lock.yaml — The lockfile was regenerated (upgraded from lockfile version 5.1 to 9.0) to resolve the new dependency graph with form-data@4.0.4 properly integrated. The lock ensures reproducible installs with the secure version.

The fixed version of form-data replaces the boundary generation with:

// Secure boundary generation in form-data >= 4.0.4
FormData.prototype._generateBoundary = function() {
  this._boundary = crypto.randomBytes(24).toString('hex');
};

crypto.randomBytes() draws from the operating system's CSPRNG (e.g., /dev/urandom on Linux, CryptGenRandom on Windows), providing entropy that is computationally infeasible to predict.

Prevention & Best Practices

  1. Never use Math.random() for security-sensitive values: Boundaries, tokens, nonces, session IDs, CSRF tokens — all must use crypto.randomBytes() or crypto.randomUUID().

  2. Pin critical security dependencies directly: Even if a package is only a transitive dependency, adding it as a direct dependency with a pinned version (like "form-data": "4.0.4") ensures your project uses the patched version.

  3. Regularly scan lockfiles: Tools like Trivy, Snyk, and npm audit can detect known vulnerabilities in your resolved dependency tree — including transitive dependencies buried in lockfiles.

  4. Keep lockfiles updated: The original pnpm-lock.yaml used lockfile version 5.1 with outdated resolution. Modern lockfile formats (9.0) provide better dependency resolution and security metadata.

  5. Audit proxy chains: When your application routes traffic through proxies (as this bot system does with https-proxy-agent and socks-proxy-agent), the security of data in transit becomes even more critical. Predictable boundaries in proxy environments dramatically increase exploitation risk.

Key Takeaways

  • form-data@2.3.3's use of Math.random() for boundary generation made all multipart requests from this bot proxy system predictable — an attacker observing just a few requests through the proxy chain could predict all future boundaries.
  • Transitive dependencies are attack surface: The vulnerable form-data was pulled in through request@2.88.0form-data@2.3.3, not directly declared — making it invisible without lockfile scanning.
  • Pinning form-data as a direct dependency at version 4.0.4 overrides the vulnerable transitive version, a pattern useful when you can't easily upgrade the parent dependency.
  • The pnpm lockfile upgrade from v5.1 to v9.0 wasn't just cosmetic — modern lockfile formats provide better integrity guarantees and dependency resolution.
  • In proxy-heavy architectures like this agar.io bot system, cryptographic weakness in HTTP primitives is amplified because every intermediary is a potential observer.

How Orbis AppSec Detected This

  • Source: The form-data package's internal boundary generation function, called whenever multipart form requests are constructed via request or request-promise in the bot proxy system.
  • Sink: Math.random() calls within form-data@2.3.3's _generateBoundary() method, producing predictable boundary strings used in HTTP multipart request bodies.
  • Missing control: No cryptographically secure random number generator was used for boundary generation; Math.random() provided insufficient entropy.
  • CWE: CWE-330 (Use of Insufficiently Random Values)
  • Fix: Upgraded form-data from 2.3.3 to 4.0.4 by adding it as a direct pinned dependency in package.json and regenerating the pnpm-lock.yaml.

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 stark reminder that cryptographic security failures can lurk in the most fundamental layers of your dependency tree. A single Math.random() call in a boundary generation function — buried three levels deep in your node_modules — can undermine the integrity of every multipart HTTP request your application sends.

For this agar.io bot proxy system, where traffic flows through multiple proxy layers and the request library constructs multipart forms automatically, the impact was especially severe. The fix was straightforward: pin form-data@4.0.4 directly and let the secure crypto.randomBytes() implementation do its job.

Audit your lockfiles. Pin your security-critical dependencies. And never trust Math.random() with anything that needs to be unpredictable.

References

Frequently Asked Questions

What is an unsafe random function vulnerability?

It occurs when security-sensitive operations like generating tokens, boundaries, or identifiers use predictable random number generators (like Math.random()) instead of cryptographically secure ones (like crypto.randomBytes()), allowing attackers to predict generated values.

How do you prevent unsafe random function vulnerabilities in Node.js?

Always use the `crypto` module's `randomBytes()` or `randomUUID()` for any security-relevant random value generation. Never use `Math.random()` for boundaries, tokens, session IDs, or any value that must be unpredictable.

What CWE is unsafe random function?

CWE-330 (Use of Insufficiently Random Values). Related entries include CWE-338 (Use of Cryptographically Weak Pseudo-Random Number Generator) and CWE-331 (Insufficient Entropy).

Is upgrading the dependency enough to prevent this vulnerability?

Yes, upgrading form-data to versions 2.5.4, 3.0.4, or 4.0.4 fully resolves CVE-2025-7783. However, you should audit your own codebase for similar Math.random() usage in security contexts.

Can static analysis detect unsafe random function usage?

Yes, tools like Trivy (which detected this CVE), Semgrep, and ESLint security plugins can flag Math.random() usage in security-sensitive contexts and identify vulnerable dependency versions.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #36

Related Articles

high

How Nodemailer raw option bypass happens in Node.js and how to fix it

A high-severity vulnerability in Nodemailer versions prior to 9.0.1 allowed attackers to bypass the `disableFileAccess` and `disableUrlAccess` security controls using the message-level `raw` option. This bypass enabled arbitrary file reads from the server and full-response Server-Side Request Forgery (SSRF) attacks, potentially exposing sensitive configuration files and internal network resources. The fix involves upgrading Nodemailer from version 8.0.7 to 9.0.1.

high

How Denial of Service via malformed HTTP header decoding happens in OpenTelemetry JavaScript and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-59892) was discovered in @opentelemetry/propagator-jaeger version 2.7.1, where malformed HTTP headers could crash Node.js applications during trace context decoding. The vulnerability was fixed by upgrading from version 2.7.1 to 2.9.0, which includes improved header validation and error handling to prevent application crashes from malicious or corrupted trace propagation headers.

high

How ReDoS happens in Node.js MCP SDK and how to fix it

A Regular Expression Denial of Service (ReDoS) vulnerability was discovered in Anthropic's Model Context Protocol (MCP) TypeScript SDK version 1.24.0. This high-severity flaw (CVE-2026-0621) could allow attackers to craft malicious input that causes catastrophic regex backtracking, freezing the Node.js event loop. The fix involves upgrading to @modelcontextprotocol/sdk version 1.25.2, which patches the vulnerable regex patterns.

critical

How Telegram OAuth hash validation bypass happens in PHP and how to fix it

A critical vulnerability in the Telegram OAuth provider allowed attackers to bypass authentication by submitting fabricated hash values without cryptographic validation. The fix replaces an unsafe string comparison with PHP's constant-time `hash_equals()` function, preventing timing attacks and ensuring all user data parameters are properly validated against the HMAC-SHA256 signature.

critical

How Server-Side Request Forgery (SSRF) happens in JavaScript fetch() and how to fix it

A critical Server-Side Request Forgery vulnerability in `popup.js` allowed attackers to inject malicious URLs from scraped webpages directly into fetch() calls, potentially accessing internal network resources and AWS metadata endpoints. The fix adds URL validation to ensure only HTTP/HTTPS protocols are used and blocks requests to private IP ranges and localhost addresses.

critical

How Denial of Service via gzip bomb happens in Node.js tar and how to fix it

CVE-2026-59873 is a critical Denial of Service vulnerability in the `node-tar` package (versions before 7.5.19) that allows an attacker to trigger resource exhaustion by supplying a crafted gzip bomb archive. The fix upgrades `tar` from 7.5.16 to 7.5.19 in both `package.json` and `package-lock.json`, closing the attack surface for any Node.js application that processes tar archives. Because this package is used in production code — not just in tests — the exposure was real and immediate.