From da2aff633dc47cc424ef9b0ffc5323fca587fd37 Mon Sep 17 00:00:00 2001 From: Rahul Yadav <52163880+rahulyadav5524@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:39:21 +0530 Subject: [PATCH 1/3] fix __proto__ pollution --- lib/dispatcher/balanced-pool.js | 15 ++++++++------ test/node-test/balanced-pool.js | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/lib/dispatcher/balanced-pool.js b/lib/dispatcher/balanced-pool.js index d53a26966ad..5aa6edf0e95 100644 --- a/lib/dispatcher/balanced-pool.js +++ b/lib/dispatcher/balanced-pool.js @@ -13,7 +13,7 @@ const { kGetDispatcher } = require('./pool-base') const Pool = require('./pool') -const { kUrl } = require('../core/symbols') +const util = require('../core/symbols') const { parseOrigin } = require('../core/util') const kFactory = Symbol('factory') @@ -56,7 +56,10 @@ class BalancedPool extends PoolBase { super() - this[kOptions] = opts + this[kOptions] = { ...util.deepClone(opts) } + this[kOptions].interceptors = opts.interceptors + ? { ...opts.interceptors } + : undefined this[kIndex] = -1 this[kCurrentWeight] = 0 @@ -76,7 +79,7 @@ class BalancedPool extends PoolBase { } addUpstream (upstream) { - const upstreamOrigin = parseOrigin(upstream).origin + const upstreamOrigin = util.parseOrigin(upstream).origin if (this[kClients].find((pool) => ( pool[kUrl].origin === upstreamOrigin && @@ -85,7 +88,7 @@ class BalancedPool extends PoolBase { ))) { return this } - const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) + const pool = this[kFactory](upstreamOrigin, this[kOptions]) this[kAddClient](pool) pool.on('connect', () => { @@ -125,7 +128,7 @@ class BalancedPool extends PoolBase { } removeUpstream (upstream) { - const upstreamOrigin = parseOrigin(upstream).origin + const upstreamOrigin = util.parseOrigin(upstream).origin const pool = this[kClients].find((pool) => ( pool[kUrl].origin === upstreamOrigin && @@ -141,7 +144,7 @@ class BalancedPool extends PoolBase { } getUpstream (upstream) { - const upstreamOrigin = parseOrigin(upstream).origin + const upstreamOrigin = util.parseOrigin(upstream).origin return this[kClients].find((pool) => ( pool[kUrl].origin === upstreamOrigin && diff --git a/test/node-test/balanced-pool.js b/test/node-test/balanced-pool.js index 6f4f24743aa..404cc8bda27 100644 --- a/test/node-test/balanced-pool.js +++ b/test/node-test/balanced-pool.js @@ -601,3 +601,39 @@ describe('weighted round robin', () => { }) } }) + +test('should not be vulnerable to __proto__ pollution via options', async (t) => { + const { EventEmitter } = require('node:events') + + let capturedOpts + + // Simulate attacker-controlled options with __proto__ property + const attackerOptions = JSON.parse(` + { + "__proto__": { + "polluted": "YES", + "connections": 1 + } + } + `) + + attackerOptions.factory = (origin, opts) => { + capturedOpts = opts + + const stub = new EventEmitter() + stub.dispatch = () => true + stub.close = () => Promise.resolve() + stub.destroy = () => Promise.resolve() + stub.destroyed = false + stub.closed = false + + return stub + } + + new BalancedPool(['http://localhost/'], attackerOptions) + + // Verify that the captured options do not have polluted prototype + assert.strictEqual(capturedOpts.polluted, undefined, 'polluted property should not exist on options') + assert.strictEqual(Object.getPrototypeOf(capturedOpts).polluted, undefined, 'prototype should not be polluted') + assert.strictEqual({}.polluted, undefined, 'global Object.prototype should not be polluted') +}) From daaa33d9425dbd7d86d564a67d21817d6f1f41e6 Mon Sep 17 00:00:00 2001 From: Rahul Yadav <52163880+rahulyadav5524@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:44:31 +0530 Subject: [PATCH 2/3] fix --- lib/dispatcher/balanced-pool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dispatcher/balanced-pool.js b/lib/dispatcher/balanced-pool.js index 5aa6edf0e95..fa0fa97288e 100644 --- a/lib/dispatcher/balanced-pool.js +++ b/lib/dispatcher/balanced-pool.js @@ -13,8 +13,8 @@ const { kGetDispatcher } = require('./pool-base') const Pool = require('./pool') -const util = require('../core/symbols') -const { parseOrigin } = require('../core/util') +const { kUrl } = require('../core/symbols') +const util = require('../core/util') const kFactory = Symbol('factory') const kOptions = Symbol('options') From ae9b69898a7c61b845f08fe39f8c70c0b25548ed Mon Sep 17 00:00:00 2001 From: Rahul Yadav <52163880+rahulyadav5524@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:48:02 +0530 Subject: [PATCH 3/3] fix lint issue --- test/node-test/balanced-pool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node-test/balanced-pool.js b/test/node-test/balanced-pool.js index 404cc8bda27..055c6f909b3 100644 --- a/test/node-test/balanced-pool.js +++ b/test/node-test/balanced-pool.js @@ -630,7 +630,7 @@ test('should not be vulnerable to __proto__ pollution via options', async (t) => return stub } - new BalancedPool(['http://localhost/'], attackerOptions) + new BalancedPool(['http://localhost/'], attackerOptions) // eslint-disable-line no-new // Verify that the captured options do not have polluted prototype assert.strictEqual(capturedOpts.polluted, undefined, 'polluted property should not exist on options')