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

high

How Quadratic CPU Consumption Vulnerabilities Happen in JavaScript YAML Parsers and How to Fix Them

A high-severity denial-of-service vulnerability in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through specially crafted YAML documents using the !!omap tag. This fix upgrades js-yaml from 4.1.1 to 4.3.1 and from 3.14.2 to 3.15.1, eliminating the algorithmic complexity attack vector that could freeze Node.js applications processing untrusted YAML input.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A high-severity command injection vulnerability was discovered in `scripts/build.js` where `execSync` was called with string-interpolated arguments (`sourceDir` and `outputPath`) inside a shell command. By replacing `execSync` with `spawnSync` using an argument array (no shell), the fix eliminates the possibility of shell metacharacter injection while preserving identical build behavior.

high

How Command Injection happens in Node.js child_process and how to fix it

A command injection vulnerability in nix.js's Release class allowed potentially malicious input through the `arch` parameter to be executed via shell commands. The fix replaced `execSync()` with `execFileSync()`, eliminating shell interpretation and preventing command injection by passing arguments as an array instead of a concatenated string.

critical

How Sensitive Data Exposure in Error Logging happens in TypeScript/Deno and how to fix it

A critical vulnerability in Supabase Edge Functions allowed sensitive authentication errors and API credentials to leak through verbose error logging. The `cancel-subscription/index.ts` function logged full error objects to the console, potentially exposing Paddle API keys and auth tokens in deployment logs. The fix sanitizes all error messages to log only safe error text while preserving debugging capability.

critical

How HTTP Header Injection Happens in Go and How to Fix It

A critical vulnerability in the file upload handler allowed attackers to inject CRLF sequences into HTTP response headers through crafted filenames. The fix sanitizes user-supplied filenames before using them in Content-Disposition headers, preventing header injection attacks that could lead to cache poisoning, session fixation, or XSS.

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.