Deserialization that evaluates the payload
ID |
javascript.insecure_deserialization |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Misconfiguration |
Language |
JavaScript |
Tags |
ASVS50:v1.5.2, CWE:502, NIST.SP.800-53, OWASP:2025:A08, PCI-DSS:6.5.1 |
Description
A payload is read with a deserializer that rebuilds the functions embedded in it and runs them. The payload is code rather than data, so whoever writes it executes it.
Rationale
javascript.code_injection_with_deserialization reports deserialization when a data path can be followed
from an external input to the call. That needs the input to be visible: an unserialize() in a codec or
a helper module, where the caller lives elsewhere, has no source to follow and is reported by nothing.
Its severity is low because the finding says nothing about whether an attacker-controlled payload
actually reaches the call — when that can be shown, javascript.code_injection_with_deserialization
reports it as critical, and this finding is dropped as a duplicate.
A short sink list, on purpose
JavaScript has no equivalent of pickle or ObjectInputStream in its standard library. JSON.parse
names no types and evaluates nothing, so unlike Java, Python or PHP there is no default to report. What
is left is a handful of packages that deliberately round-trip functions, and each was checked against
the version npm currently serves:
| Sink | Checked |
|---|---|
|
Verified: a payload carrying |
|
Still exported by the current 0.0.5, and rebuilds functions through a generated module. |
|
Removed in the 3.x line, which exports a bare function. Kept precisely because of that: a call
naming it can only be against the 1.x/2.x that still had it, where it was |
That last entry is the inverse of the usual version trap. Elsewhere a sink has to be dropped because a release made the default safe; here the release removed the API outright, so matching it can only ever hit the vulnerable old major.
Examples
Reported:
const serialize = require('node-serialize');
function restore(blob) {
return serialize.unserialize(blob); // reported
}
Not reported:
function restore(blob) {
return JSON.parse(blob);
}
Remediation
Use JSON.parse and validate the result against the shape you expect. Where functions genuinely need to
cross a boundary, send a name the receiver resolves against a fixed table rather than a body it
evaluates:
const handlers = { resize, crop };
const { handler, args } = JSON.parse(blob);
return handlers[handler]?.(...args);