Description
The unserialize function in node-serialize 0.0.4 restores more than plain data. It first applies JSON.parse to string input, then recursively walks objects and arrays and evaluates string properties beginning with _$$ND_FUNC$$_. Passing request-controlled strings, objects or arrays to it can therefore execute arbitrary JavaScript in the server process.
The package's security advisory lists no patched release. Remove node-serialize and executable object restoration from request-processing paths, and handle external input only as data.
Cause of the risk
unserializeinterprets its function marker as executable code, not an ordinary string.- Applying
JSON.parsefirst does not help if the resulting object is then passed tounserialize; the marker strings remain available for evaluation. - A signature can detect unauthorized changes, but it does not make an authorized malicious producer, a compromised key, contaminated existing records or executable decoding safe.
- Containers and least privilege may limit harm. The Node.js Permission Model is not a security boundary against malicious code.
Potential impact
- Arbitrary code or commands executed with the application's permissions
- Access to files, environment variables, credentials and internal services
- Data exposure or alteration and compromise of other systems
- Repeated execution when a malicious record remains in storage
Remediation
- Remove
node-serializefrom request-processing code and runtime dependencies. There is no patched version to upgrade to. - Parse external data with a data-only format such as JSON. Apply an endpoint-appropriate byte limit before parsing, then validate required fields, types, lengths, ranges and unexpected extra fields. TypeScript interfaces, annotations, generic constraints and assertions disappear at runtime and do not provide validation.
- Copy only validated fields into a new data object. Do not pass parsed or validated values to
unserialize,eval,Functionor dynamic operation selection. - Migrate existing
node-serializerecords to a data-only format through a controlled offline process separate from network requests. If a temporary migration must decode old records, verify the exact stored bytes and authorize their producer first. This is not a permanent substitute for removing executable deserialization. - Run migration with least privilege, then search and test to confirm that
unserializecalls and the dependency are removed.
Examples
Before
const express = require("express");
let serialize = require("node-serialize");
const app = express();
app.use(express.json({ limit: "64kb", strict: true }));
app.post("/profiles", (req, res) => {
const profile = serialize.unserialize(req.body);
res.json(profile);
});
The caller can include function markers in the body. Object input is therefore dangerous as well as string input.
After
const express = require("express");
const app = express();
app.use(express.json({ limit: "64kb", strict: true }));
const allowedProfileKeys = new Set(["displayName", "age"]);
function parseProfile(body) {
if (body === null || typeof body !== "object" || Array.isArray(body)) {
throw new TypeError("profile must be an object");
}
const keys = Object.keys(body);
if (
keys.length !== 2 ||
keys.some((key) => !allowedProfileKeys.has(key)) ||
!Object.hasOwn(body, "displayName") ||
!Object.hasOwn(body, "age")
) {
throw new TypeError("profile has an invalid shape");
}
if (
typeof body.displayName !== "string" ||
body.displayName.length < 1 ||
body.displayName.length > 80 ||
!Number.isInteger(body.age) ||
body.age < 0 ||
body.age > 130
) {
throw new TypeError("profile has invalid values");
}
return {
displayName: body.displayName,
age: body.age,
};
}
app.post("/profiles", (req, res) => {
try {
const profile = parseProfile(req.body);
res.status(201).json(profile);
} catch {
res.status(400).json({ error: "invalid profile" });
}
});
This example limits the request size, validates the exact data shape and domain ranges, and copies only permitted values into a new object. 64kb and the field constraints are examples; tighten them to match the endpoint's actual data contract.
References
node-serializesecurity warningnode-serializeunserialize implementation- GitHub Advisory GHSA-q4v7-4rhw-9hqm / CVE-2017-5941
- CWE-502: Deserialization of Untrusted Data
- OWASP Top 10:2025 A08 - Software or Data Integrity Failures
- OWASP Top 10:2021 A08 - Software and Data Integrity Failures
- Express
express.json - Node.js Permission Model