From 34d6238050e32d35a30f17f256223ae8a4c1a07a Mon Sep 17 00:00:00 2001 From: redonkulus Date: Fri, 24 Apr 2026 12:43:11 -0700 Subject: [PATCH] fix: reject spoofed URL objects with non-string toString() result Validates that URL.toString() returns a primitive string before passing to serialize(), preventing code injection via Object.create(URL.prototype) spoofing. Adds a regression test covering the attack vector from PSECBUGS-108653. Co-Authored-By: Claude Sonnet 4.6 --- index.js | 6 +++++- test/unit/serialize.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 2389110..d3fea32 100644 --- a/index.js +++ b/index.js @@ -287,7 +287,11 @@ module.exports = function serialize(obj, options) { } if (type === 'L') { - return "new URL(" + serialize(urls[valueIndex].toString(), options) + ")"; + var urlStr = urls[valueIndex].toString(); + if (typeof urlStr !== 'string') { + throw new TypeError('URL.toString() must return a string'); + } + return "new URL(" + serialize(urlStr, options) + ")"; } var fn = functions[valueIndex]; diff --git a/test/unit/serialize.js b/test/unit/serialize.js index 6a22891..b4eea83 100644 --- a/test/unit/serialize.js +++ b/test/unit/serialize.js @@ -535,6 +535,18 @@ describe('serialize( obj )', function () { strictEqual(d instanceof URL, true); strictEqual(d.toString(), 'https://x.com/'); }); + + it('should throw when serializing a spoofed URL with non-string toString()', function () { + var fakeUrl = Object.create(URL.prototype); + fakeUrl.toString = function () { + return { + toString: function () { + return 'https://example.com/'; + } + }; + }; + throws(function () { serialize({ url: fakeUrl }); }, TypeError); + }); }); describe('XSS', function () {