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
replicatordecode 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\\')"}}]'
)();

In this payload:
nameis attacker-controlled and set to"Function"messagebecomes 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:
-
Explicit type allowlist
Only known safe error constructors can be selected. -
Safe default
Unknown values fall back toErrorinstead 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.