Back to Blog
high SEVERITY6 min read

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

The `serialize-javascript` package version 6.0.2 contained a high-severity Remote Code Execution (RCE) vulnerability (GHSA-5c6j-r48x-rmvq) exploitable through crafted `RegExp.flags` and `Date.prototype.toISOString()` payloads. Upgrading to version 7.0.3 eliminates the vulnerable serialization logic and removes the `randombytes` dependency that was part of the attack surface. This fix was applied via a `package.json` override and `package-lock.json` update.

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

Answer Summary

GHSA-5c6j-r48x-rmvq is a high-severity Remote Code Execution (RCE) vulnerability in the `serialize-javascript` npm package (versions prior to 7.0.0), exploitable via prototype pollution of `RegExp.flags` and `Date.prototype.toISOString()`. It maps to CWE-94 (Improper Control of Generation of Code). The fix is to upgrade `serialize-javascript` from 6.0.2 to 7.0.3, which rewrites the serialization logic to prevent code injection through these prototype methods.

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 by polluting RegExp or Date prototypes before serialization
languageJavaScript (Node.js)
root causeserialize-javascript 6.0.2 trusts return values of RegExp.flags and Date.prototype.toISOString() during code generation
vulnerabilityRemote Code Execution (RCE) via unsafe serialization

Introduction

In a Docusaurus-based documentation site's dependency tree, a high-severity Remote Code Execution vulnerability lurked inside serialize-javascript version 6.0.2. This package — widely used by webpack, Terser, and other build tools to convert JavaScript objects into safe string representations — contained a critical flaw: an attacker who could pollute RegExp.prototype.flags or Date.prototype.toISOString() could inject arbitrary code into the serialized output, achieving RCE when that output was later evaluated.

The vulnerability tracked as GHSA-5c6j-r48x-rmvq was detected by Trivy in the project's package-lock.json and resolved by upgrading from version 6.0.2 to 7.0.3. While the dependency wasn't confirmed reachable in the application's runtime paths, its presence in the build pipeline represented a concrete exploit primitive that automated attack tooling could chain with other weaknesses.

The Vulnerability Explained

How serialize-javascript Works

The serialize-javascript library converts JavaScript values — including functions, RegExp objects, Dates, Maps, and Sets — into string representations that can be safely embedded in HTML or evaluated later. For example:

const serialize = require('serialize-javascript');
serialize({ regex: /hello/gi, date: new Date() });
// Output: '{"regex":new RegExp("hello", "gi"),"date":new Date("2024-01-15T...")}'

The Attack Vector

In version 6.0.2, the library directly called RegExp.prototype.flags and Date.prototype.toISOString() to construct the serialized output string. The critical issue is that these are accessor properties and prototype methods — they can be overridden via prototype pollution.

Consider this attack scenario:

// Attacker pollutes the RegExp prototype
Object.defineProperty(RegExp.prototype, 'flags', {
  get: function() {
    return 'gi", ""));console.log(process.env);//';
  }
});

// When serialize-javascript processes a RegExp:
const serialize = require('serialize-javascript');
const output = serialize({ re: /innocent/ });
// Output becomes: new RegExp("innocent", "gi", ""));console.log(process.env);//")
// The injected code executes when this string is eval'd or embedded in a script

Similarly, Date.prototype.toISOString() could be polluted:

Date.prototype.toISOString = function() {
  return '");require("child_process").exec("rm -rf /");//';
};

Why This Is High Severity

The serialized output from this library is commonly:
1. Embedded directly in <script> tags by webpack/SSR frameworks
2. Written to build artifacts that are later executed
3. Used in server-side rendering pipelines

If an attacker can achieve prototype pollution (via another vulnerability in the dependency chain, a malicious npm package, or user-controlled JSON parsing), they can escalate it to full RCE through this serialization step.

The Role of randombytes

Version 6.0.2 depended on randombytes (which itself depends on safe-buffer) to generate random placeholders during serialization. Version 7.0.3 eliminates this dependency entirely, using Node.js built-in crypto.randomUUID() instead — reducing the attack surface and the dependency tree simultaneously.

The Fix

The fix involves two coordinated changes across package.json and package-lock.json:

1. Adding a Package Override (package.json)

// Before
"overrides": {
    "@cmfcmf/docusaurus-search-local": {
      "@docusaurus/core": "^3.5.2",
      "cheerio": "1.0.0-rc.12"
    }
}

// After
"overrides": {
    "@cmfcmf/docusaurus-search-local": {
      "@docusaurus/core": "^3.5.2",
      "cheerio": "1.0.0-rc.12"
    },
    "serialize-javascript": "7.0.3"
}

The overrides field in package.json forces all instances of serialize-javascript in the dependency tree to resolve to 7.0.3, regardless of what version ranges transitive dependencies specify. This is critical because serialize-javascript is typically pulled in by webpack plugins, Terser, and other build tools — not directly by the application.

2. Updating the Lock File (package-lock.json)

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

// After
"node_modules/serialize-javascript": {
  "version": "7.0.3",
  "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.3.tgz",
  "integrity": "sha512-h+cZ/XXarqDgCjo+YSyQU/ulDEESGGf8AMK9pPNmhNSl/FzPl6L8pMp1leca5z6NuG6tvV/auC8/43tmovowww==",
  "license": "BSD-3-Clause",
  "engines": {
    "node": ">=20.0.0"
  }
}

Key improvements in 7.0.3:
- Removed randombytes dependency: The node_modules/randombytes entry is deleted entirely, reducing supply chain risk
- Added integrity hash: The integrity field with SHA-512 ensures the exact expected package is installed
- Node.js 20+ requirement: Leverages built-in crypto.randomUUID() and modern security features
- Hardened serialization: The new version doesn't trust prototype method return values for code generation

Why Both Files Changed

The package.json override ensures the constraint persists across npm install runs and affects all transitive consumers. The package-lock.json change reflects the resolved state, ensuring deterministic installs with the exact patched version and its integrity hash.

Prevention & Best Practices

1. Audit Your Dependency Tree Regularly

npm audit
npx trivy fs --scanners vuln .

Transitive dependencies like serialize-javascript are easy to miss because they don't appear in your package.json directly.

2. Use Package Overrides for Deep Dependencies

When a vulnerability exists in a transitive dependency and the direct dependency hasn't updated yet, npm overrides (or yarn resolutions) let you force a safe version:

{
  "overrides": {
    "serialize-javascript": ">=7.0.0"
  }
}

3. Minimize Serialization of Untrusted Data

If you don't need executable JavaScript output, prefer JSON.stringify() which produces inert data strings. Only use serialize-javascript when you specifically need to serialize functions, RegExp, or other non-JSON types.

4. Protect Against Prototype Pollution

Since this RCE requires prototype pollution as a precondition:
- Use Object.create(null) for dictionary objects
- Freeze prototypes in security-critical paths: Object.freeze(Object.prototype)
- Validate and sanitize JSON input with libraries like secure-json-parse

5. Pin and Verify Dependencies

Always commit your package-lock.json and use npm ci in CI/CD pipelines. The integrity field in the lock file prevents tampered packages from being installed.

Key Takeaways

  • serialize-javascript 6.0.2 trusts prototype methods during code generation — a design flaw that converts prototype pollution into RCE
  • The randombytes dependency was eliminated in 7.0.3, reducing the attack surface and dependency count simultaneously
  • npm overrides in package.json is the correct mechanism to force transitive dependency upgrades when direct parents haven't updated
  • Build-time dependencies can be just as dangerous as runtime ones — webpack and Terser use serialize-javascript to generate code that executes in users' browsers
  • Integrity hashes in package-lock.json (the sha512-h+cZ/XX... value) provide tamper detection that wasn't present in the old lock entry

How Orbis AppSec Detected This

  • Source: The serialize-javascript package at version 6.0.2 in the project's package-lock.json dependency tree, pulled in transitively by build tools
  • Sink: The serialize() function in serialize-javascript/index.js which calls RegExp.prototype.flags and Date.prototype.toISOString() to construct executable JavaScript strings
  • Missing control: No sanitization or validation of prototype method return values before embedding them in generated code strings
  • CWE: CWE-94 (Improper Control of Generation of Code / Code Injection)
  • Fix: Upgraded serialize-javascript from 6.0.2 to 7.0.3 via npm override, which rewrites serialization logic to not trust pollutable prototype accessors

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

GHSA-5c6j-r48x-rmvq demonstrates how a seemingly innocuous utility library — one that serializes JavaScript objects to strings — can become an RCE vector when it trusts prototype methods during code generation. The fix was straightforward: upgrade serialize-javascript from 6.0.2 to 7.0.3 using an npm override. But the lesson is deeper: any library that generates executable code from object properties must treat those properties as potentially attacker-controlled. In a world of prototype pollution vulnerabilities and increasingly automated exploit chains, proactively removing these primitives from your dependency tree is essential defense-in-depth.

References

Frequently Asked Questions

What is RCE via serialize-javascript?

It's a vulnerability where an attacker can inject and execute arbitrary JavaScript code by manipulating how the serialize-javascript library converts objects (specifically RegExp and Date instances) into executable string representations.

How do you prevent RCE via serialization in Node.js?

Keep serialization libraries updated, avoid serializing untrusted input, use Content Security Policy headers, and validate all objects before serialization. Prefer JSON.stringify() when executable code output isn't needed.

What CWE is this vulnerability?

CWE-94 — Improper Control of Generation of Code ('Code Injection'), which covers cases where software constructs code segments using externally-influenced input without proper neutralization.

Is input validation enough to prevent serialization RCE?

No. While input validation helps, the root cause is in the serialization library itself trusting prototype method return values. The library must sanitize its own output regardless of input validation at the application layer.

Can static analysis detect serialization RCE vulnerabilities?

Yes. Software Composition Analysis (SCA) tools like Trivy, Snyk, and npm audit can detect known vulnerable versions of serialize-javascript. Static analysis can also flag usage patterns where untrusted data flows into serialization functions.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1389

Related Articles

high

How Octal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

A critical parsing inconsistency in the `ip-address` npm package (version 10.2.0) allowed attackers to bypass SSRF protections by exploiting how leading-zero octets are interpreted differently—decimal by the library versus octal by system resolvers. This vulnerability (CVE-2026-69192) was fixed by upgrading to version 10.3.1 using an npm override, ensuring consistent IP address validation across the application.

high

How Command Injection Happens in Node.js Child Process Calls and How to Fix It

A Node.js library was vulnerable to command injection through unsafe use of `execSync()` with shell string interpolation in the `index.js` file. By switching to `execFileSync()` with argument arrays, the fix eliminates the ability for attackers to inject shell metacharacters through file paths. This change demonstrates a critical security hardening pattern for any Node.js code that spawns child processes.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in the axios HTTP client library allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This could route sensitive internal traffic through attacker-controlled proxy servers. The fix upgrades axios from 1.13.6 to 1.18.0, which includes a rewritten proxy resolution mechanism using `proxy-from-env` v2.1.0 and the `https-proxy-agent` package.

high

How Denial of Service via Infinite Loop happens in Node.js dependencies and how to fix it

A high-severity vulnerability in the nanoid package (CVE-2026-67213) allowed attackers to trigger infinite loops through the customAlphabet function, potentially causing complete denial of service. This fix upgrades nanoid from version 3.3.16 to 3.3.17 in the app_store dependency tree, eliminating the DoS risk through a simple version override.

high

How Denial of Service via Deeply Nested Field Names Happens in Node.js Multer and How to Fix It

A high-severity Denial of Service vulnerability (CVE-2026-5079) was discovered in the multer package, a popular Node.js middleware for handling multipart form data. Attackers could craft malicious requests with deeply nested field names to exhaust server resources. The fix upgrades multer from version 2.0.2 to 2.2.0, which implements proper limits on field name parsing depth.

critical

How SQL Injection happens in PHP PDO queries and how to fix it

A critical SQL injection vulnerability was discovered in the `getOfficialContests()` method of ContestRepository.php, where the `$site_id` parameter was directly interpolated into a SQL query string instead of using prepared statements. This vulnerability allowed attackers to inject arbitrary SQL commands and potentially access or manipulate the entire contest database. The fix replaced `pdo->query()` with `pdo->prepare()` and proper parameter binding.