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 Denial of Service via Exponential Time Complexity happens in brace-expansion and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-13149) was discovered in the brace-expansion npm package, where specially crafted input patterns could trigger exponential time complexity, potentially freezing Node.js applications. The fix upgrades multiple versions of brace-expansion (1.1.18 → 1.1.16, 2.1.1 → 2.1.2, and 5.0.6 → 5.0.7) through yarn resolutions to ensure all dependency paths use patched versions.

critical

How unvalidated URL input handling happens in SvelteKit with Tauri and how to fix it

A critical vulnerability in `src/routes/+page.svelte` allowed attackers to supply arbitrary URLs—including `http://` and local file paths—through query parameters and drag-drop events, which were then fetched without validation. The fix restricts input to HTTPS-only URLs and removes the dangerous local file fetch path entirely, eliminating both SSRF and local file disclosure attack vectors.

critical

How SQL injection happens in Node.js string interpolation and how to fix it

A critical SQL injection vulnerability was discovered in the `getScript()` method of `src/core/statistics.js`, where the `metadata_id` variable was directly interpolated into DELETE and UPDATE SQL statements without any validation. An attacker controlling this parameter could inject malicious SQL payloads to delete entire tables or exfiltrate sensitive data. The fix implements strict input validation using `parseInt()` and regex patterns to ensure only safe values reach the database queries.

critical

How Command Injection happens in Python Flask and how to fix it

A critical command injection vulnerability was discovered in a Flask application's `/abc2xml` endpoint where user-supplied ABC music notation data could be weaponized to execute arbitrary shell commands. The `run_command` function used `subprocess.run()` with `shell=True` and string concatenation, allowing attackers to inject shell metacharacters. The fix switches to a list-based command invocation with `shell=False`, eliminating the injection vector entirely.

critical

How credential header disclosure happens in electron-updater and how to fix it

A critical vulnerability in electron-updater (CVE-2026-54673) allowed OAuth tokens and API credentials to leak when HTTP redirects occurred during application updates. The fix upgrades electron-updater from version 6.3.0 to 6.8.9, which properly strips sensitive authorization headers before following redirects to external domains.

high

How Unicode Normalization Infinite Loops Happen in Go and How to Fix CVE-2026-56852

CVE-2026-56852 is a high-severity vulnerability in golang.org/x/text that allows the Unicode normalization iterator to enter an infinite loop when processing specially crafted input. This fix upgrades the dependency from v0.37.0 to v0.39.0, tightening input validation and preventing denial-of-service attacks in applications that process untrusted Unicode text.