Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,18 @@ module.exports = function serialize(obj, options) {
}

if (type === 'D') {
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
// Validate ISO string format to prevent code injection via spoofed toISOString()
var isoStr = String(dates[valueIndex].toISOString());
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(isoStr)) {
throw new TypeError('Invalid Date ISO string');
}
return "new Date(\"" + isoStr + "\")";
}

if (type === 'R') {
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
// Sanitize flags to prevent code injection (only allow valid RegExp flag characters)
var flags = String(regexps[valueIndex].flags).replace(/[^gimsuydv]/g, '');
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + flags + "\")";
}

if (type === 'M') {
Expand Down
27 changes: 27 additions & 0 deletions test/unit/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,23 @@ describe('serialize( obj )', function () {
strictEqual(typeof serialize(re), 'string');
strictEqual(serialize(re), 'new RegExp("[\\u003C\\\\\\u002Fscript\\u003E\\u003Cscript\\u003Ealert(\'xss\')\\\\\\u002F\\\\\\u002F]", "")');
});

it('should sanitize RegExp.flags to prevent code injection', function () {
// Object that passes instanceof RegExp with attacker-controlled .flags
var fakeRegex = Object.create(RegExp.prototype);
Object.defineProperty(fakeRegex, 'source', { get: function () { return 'x'; } });
Object.defineProperty(fakeRegex, 'flags', {
get: function () { return '"+(global.__INJECTED_FLAGS="pwned")+"'; }
});
fakeRegex.toJSON = function () { return '@placeholder'; };
var output = serialize({ re: fakeRegex });
// Malicious flags must be stripped; only valid flag chars allowed
strictEqual(output.includes('__INJECTED_FLAGS'), false);
strictEqual(output.includes('pwned'), false);
var obj = eval('obj = ' + output);
strictEqual(global.__INJECTED_FLAGS, undefined);
delete global.__INJECTED_FLAGS;
});
});

describe('dates', function () {
Expand Down Expand Up @@ -345,6 +362,16 @@ describe('serialize( obj )', function () {
strictEqual(typeof serialize({t: [d]}), 'string');
strictEqual(serialize({t: [d]}), '{"t":[{"foo":new Date("2016-04-28T22:02:17.156Z")}]}');
});

it('should reject invalid Date ISO string to prevent code injection', function () {
var fakeDate = Object.create(Date.prototype);
fakeDate.toISOString = function () { return '"+(global.__INJECTED_DATE="pwned")+"'; };
fakeDate.toJSON = function () { return '2024-01-01'; };
throws(function () {
serialize({ d: fakeDate });
}, TypeError);
strictEqual(global.__INJECTED_DATE, undefined);
});
});

describe('maps', function () {
Expand Down