Back to Blog
high SEVERITY6 min read

How Remote Code Execution via serialize-javascript happens in Node.js and how to fix it

A high-severity Remote Code Execution (RCE) vulnerability in the `serialize-javascript` package (version 6.0.2) allowed attackers to inject malicious code through prototype poisoning of `RegExp.flags` and `Date.prototype.toISOString()`. The fix upgrades the dependency to version 7.0.3, which eliminates the unsafe serialization patterns and removes the now-unnecessary `randombytes` dependency.

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

Answer Summary

GHSA-5c6j-r48x-rmvq is a high-severity Remote Code Execution vulnerability in the `serialize-javascript` npm package (versions prior to 7.0.0) that allows attackers to execute arbitrary code by poisoning `RegExp.flags` or `Date.prototype.toISOString()` during serialization. The fix is to upgrade `serialize-javascript` to version 7.0.3, which rewrites the serialization logic to avoid calling these potentially-tainted prototype methods, eliminating the RCE vector entirely.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code)
fixUpgrade serialize-javascript from 6.0.2 to 7.0.3 via package.json override
riskAttacker can execute arbitrary code on the server during serialization
languageJavaScript (Node.js)
root causeserialize-javascript trusted user-controllable prototype methods (RegExp.flags, Date.prototype.toISOString) during output generation
vulnerabilityRemote Code Execution (RCE) via Prototype Poisoning

Introduction

In a project's dependency tree, a high-severity Remote Code Execution vulnerability lurked inside serialize-javascript version 6.0.2 — a widely-used npm package that serializes JavaScript objects into strings for transport between server and client. The vulnerability, tracked as GHSA-5c6j-r48x-rmvq, could have allowed attackers to execute arbitrary code by poisoning RegExp.flags and Date.prototype.toISOString() — two prototype methods that the library naively trusted during its serialization process.

The vulnerable dependency was identified in package-lock.json, pinned at version 6.0.2 with a dependency on randombytes for generating unique identifiers. While the vulnerability was assessed as "present in dependency tree, not confirmed reachable," it represented an exploit primitive that automated attack tooling could chain with other weaknesses.

The Vulnerability Explained

How serialize-javascript Works

The serialize-javascript package converts JavaScript values — including functions, regular expressions, dates, maps, and sets — into string representations that can be safely embedded in HTML or transmitted across boundaries. It's commonly used by bundlers like webpack (via terser-webpack-plugin) and server-side rendering frameworks.

The Attack Vector

The vulnerability exploits a fundamental design flaw in how version 6.0.2 handled RegExp and Date serialization. When serializing a regular expression, the library called regex.flags to obtain the flags string (e.g., "gi"). When serializing dates, it called date.toISOString().

Here's the critical insight: JavaScript allows prototype methods to be overridden. An attacker who can poison these prototypes before serialization occurs can inject arbitrary code into the serialized output:

// Attacker poisons RegExp.prototype
Object.defineProperty(RegExp.prototype, 'flags', {
  get: function() {
    return 'gi; console.log("RCE achieved"); //';
  }
});

// When serialize-javascript processes a RegExp:
// Expected output: /pattern/gi
// Actual output: /pattern/gi; console.log("RCE achieved"); //

When this serialized string is later evaluated or embedded in a script context, the injected code executes. The same principle applies to Date.prototype.toISOString():

Date.prototype.toISOString = function() {
  return '2024-01-01T00:00:00.000Z"; process.exit(1); "';
};

The Vulnerable Dependency Entry

The package-lock.json contained:

"node_modules/serialize-javascript": {
  "version": "6.0.2",
  "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
  "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
  "license": "BSD-3-Clause",
  "dependencies": {
    "randombytes": "^2.1.0"
  }
}

The randombytes dependency was used internally for generating unique placeholders during serialization — a mechanism that version 7.0.3 no longer requires.

Real-World Impact

In server-side rendering scenarios, if an attacker can influence the objects being serialized (e.g., through a stored XSS payload that modifies prototypes, or through a supply-chain attack on another dependency), they could achieve:

  1. Server-side RCE: Execute arbitrary commands on the Node.js server
  2. Client-side code injection: Inject malicious scripts into HTML pages served to users
  3. Data exfiltration: Access environment variables, secrets, or database connections

The Fix

Changes Made

The fix involved two files with a clear, focused approach:

1. package.json — Adding a dependency override:

// Before
{
  "engines": {
    "node": ">=22.0"
  }
}

// After
{
  "engines": {
    "node": ">=22.0"
  },
  "overrides": {
    "serialize-javascript": "7.0.3"
  }
}

The overrides field in package.json forces all instances of serialize-javascript in the entire dependency tree — regardless of which package depends on it — to resolve to version 7.0.3. This is critical because serialize-javascript is typically a transitive dependency (pulled in by webpack plugins, testing frameworks, etc.), not a direct dependency.

2. package-lock.json — Updated resolution:

// Before
"node_modules/serialize-javascript": {
  "version": "6.0.2",
  "dependencies": {
    "randombytes": "^2.1.0"
  }
}

// After
"node_modules/serialize-javascript": {
  "version": "7.0.3",
  "engines": {
    "node": ">=20.0.0"
  }
}

Why This Solves the Problem

Version 7.0.3 of serialize-javascript fundamentally changes how it handles RegExp and Date serialization:

  1. It no longer calls RegExp.prototype.flags — Instead, it extracts flags through a safe mechanism that cannot be influenced by prototype poisoning
  2. It no longer calls Date.prototype.toISOString() — Date serialization uses internal methods that bypass the prototype chain
  3. The randombytes dependency is removed — The new version uses Node.js built-in crypto APIs (requiring Node.js ≥ 20.0.0), reducing the attack surface and dependency footprint

The removal of randombytes from the lock file (along with its safe-buffer dependency) is a direct consequence of this architectural change in the library.

Prevention & Best Practices

For This Specific Vulnerability Pattern

  1. Never trust prototype methods on user-influenced objects: When generating code strings, use internal extraction methods rather than calling potentially-overridden prototype methods
  2. Use npm overrides for transitive dependency fixes: When a vulnerability exists in a transitive dependency and the intermediate package hasn't updated yet, overrides (npm) or resolutions (yarn) force the correct version
  3. Audit serialization boundaries: Any code that converts objects to executable strings (eval-able output) should be treated as a critical security boundary

General Practices

  • Automated dependency scanning: Use tools like Trivy, Snyk, or Dependabot to continuously monitor for known vulnerabilities
  • Lock file hygiene: Regularly audit package-lock.json for outdated or vulnerable transitive dependencies
  • Prototype freezing: In security-critical contexts, consider Object.freeze(RegExp.prototype) and similar hardening
  • Content Security Policy: Implement strict CSP headers to limit the impact of injected scripts

Relevant Standards

  • CWE-94: Improper Control of Generation of Code ('Code Injection')
  • OWASP: Injection category (A03:2021)
  • Node.js Security Best Practices: Avoid eval() and equivalent patterns with untrusted input

Key Takeaways

  • serialize-javascript 6.0.2 trusted RegExp.flags and Date.prototype.toISOString() as safe — but these are user-modifiable prototype methods that can inject arbitrary code into serialized output
  • The overrides field in package.json is essential for fixing transitive dependency vulnerabilities — you can't always wait for intermediate packages to update their dependency ranges
  • Removing randombytes as a dependency in 7.0.3 signals a fundamental architectural change — the library now uses Node.js built-in crypto, reducing both attack surface and dependency complexity
  • Prototype poisoning is a viable RCE vector in serialization libraries — any code that calls prototype methods and embeds results in executable strings must be treated as security-critical
  • Vulnerability assessment noted "not confirmed reachable" but the fix was still applied — proactive removal of exploit primitives raises the bar against automated attack tooling

How Orbis AppSec Detected This

  • Source: Poisoned prototype methods (RegExp.prototype.flags, Date.prototype.toISOString()) accessible to any code running in the same JavaScript context
  • Sink: serialize-javascript library's internal serialization functions that call these prototype methods and embed results in code strings output to HTML/scripts
  • Missing control: No validation or sanitization of values returned by prototype method calls before embedding them in generated code strings; no use of safe internal extraction alternatives
  • CWE: CWE-94 (Improper Control of Generation of Code)
  • Fix: Upgraded serialize-javascript from 6.0.2 to 7.0.3 via npm override, which replaces unsafe prototype method calls with internal safe extraction logic

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

The GHSA-5c6j-r48x-rmvq vulnerability in serialize-javascript is a sobering reminder that even widely-trusted utility libraries can harbor critical security flaws. The attack — poisoning JavaScript prototype methods to inject code during serialization — is elegant in its simplicity and devastating in its impact. By upgrading to version 7.0.3 through an npm override, the project eliminates this RCE vector entirely while also reducing its dependency footprint by removing randombytes.

For developers: audit your dependency trees for serialize-javascript versions below 7.0.0, and apply the override pattern shown here if direct upgrades aren't immediately possible. Serialization boundaries are trust boundaries — treat them accordingly.

References

Frequently Asked Questions

What is RCE via prototype poisoning in serialize-javascript?

It's a vulnerability where an attacker modifies built-in JavaScript prototype methods (like RegExp.flags or Date.prototype.toISOString) so that when serialize-javascript calls them during serialization, attacker-controlled code is injected into the serialized output, which executes when deserialized.

How do you prevent prototype poisoning RCE in Node.js?

Use updated libraries that don't rely on calling potentially-tainted prototype methods, freeze prototypes where possible, validate serialization outputs, and keep dependencies up to date with automated scanning tools.

What CWE is RCE via code injection?

CWE-94 (Improper Control of Generation of Code, also known as 'Code Injection') covers vulnerabilities where attacker-controlled input is incorporated into dynamically generated code.

Is input validation enough to prevent serialize-javascript RCE?

No, because the attack vector is through prototype poisoning of built-in JavaScript objects, not through direct user input to the serialize function. The library itself must be patched to avoid relying on these prototype methods.

Can static analysis detect serialize-javascript RCE?

Yes, tools like Trivy (which detected this instance), Snyk, and npm audit can identify vulnerable versions of serialize-javascript in dependency trees through known vulnerability databases like the GitHub Security Advisory (GHSA) system.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #285

Related Articles

critical

How Credential Exposure Over HTTP Happens in Python Requests and How to Fix It

A critical vulnerability was discovered in the Bitbucket catalog connector where pagination URLs from API responses were followed without HTTPS validation, potentially exposing HTTP Basic Authentication credentials over unencrypted connections. The fix enforces HTTPS-only URLs for pagination and adds request timeouts to prevent resource exhaustion attacks.

high

How Denial of Service via unbounded brace expansion happens in Node.js and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-14257) in the `brace-expansion` package version 1.1.12 allowed attackers to craft malicious brace patterns that caused exponential-time complexity, leading to out-of-memory process crashes. The fix upgrades the dependency to version 1.1.16 using npm overrides to ensure the patched version is used throughout the entire dependency tree.

critical

How Insecure API Key Transmission Happens in JavaScript Browser Extensions and How to Fix It

A critical vulnerability in `utils/common.js` allowed API keys to be transmitted over unencrypted HTTP connections to remote servers, exposing them to network interception. The `buildModelApiRequest` function at line 490 constructed API requests without validating the transport protocol, enabling man-in-the-middle attacks. The fix enforces HTTPS for all remote API endpoints while preserving HTTP access for local development servers on loopback addresses.

critical

How URL Injection via Unvalidated User Input happens in Node.js and how to fix it

A critical URL injection vulnerability in the QQ info lookup feature allowed attackers to manipulate API request parameters by sending specially crafted messages. Without proper input validation, user-controlled data was directly embedded into external API URLs, potentially exposing sensitive authentication credentials (skey and pskey) to attacker-controlled servers.

high

How Denial of Service via Exponential-Time Complexity Happens in Node.js Dependencies and How to Fix It

A high-severity denial of service vulnerability (CVE-2026-14257) was discovered in the brace-expansion package within the zeroshot-oecp Docker container's dependency tree. The vulnerability allows attackers to craft malicious input patterns that trigger exponential-time processing, potentially freezing or crashing Node.js applications. This fix upgrades the nested brace-expansion dependency to version 5.0.9 using a targeted Dockerfile modification.

high

How Unbound Thread Allocation Denial of Service happens in Python Engine.IO and how to fix it

A high-severity vulnerability (CVE-2026-48802) in python-engineio 4.12.2 allowed attackers to exhaust system resources through unbound thread allocation, leading to denial of service. The fix upgrades the dependency to version 4.13.2, which implements thread pool limits to prevent resource exhaustion attacks against real-time WebSocket applications.