2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-20 20:00:40 +03:00

fix: Denial of Service via __proto__ Key in mergeConfig (#7369)

* fix: sec issue as per advisory

* chore: expand and add tests
This commit is contained in:
Jay
2026-02-04 20:25:06 +02:00
committed by GitHub
parent 04cf01969e
commit 28c721588c
3 changed files with 457 additions and 153 deletions
+226
View File
@@ -0,0 +1,226 @@
"use strict";
import assert from "assert";
import utils from "../../../lib/utils.js";
import mergeConfig from "../../../lib/core/mergeConfig.js";
describe("Prototype Pollution Protection", function () {
afterEach(function () {
// Clean up any pollution that might have occurred
delete Object.prototype.polluted;
});
describe("utils.merge", function () {
it("should filter __proto__ key at top level", function () {
const result = utils.merge(
{},
{ __proto__: { polluted: "yes" }, safe: "value" },
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.safe, "value");
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
});
it("should filter constructor key at top level", function () {
const result = utils.merge(
{},
{ constructor: { polluted: "yes" }, safe: "value" },
);
assert.strictEqual(result.safe, "value");
assert.strictEqual(result.hasOwnProperty("constructor"), false);
});
it("should filter prototype key at top level", function () {
const result = utils.merge(
{},
{ prototype: { polluted: "yes" }, safe: "value" },
);
assert.strictEqual(result.safe, "value");
assert.strictEqual(result.hasOwnProperty("prototype"), false);
});
it("should filter __proto__ key in nested objects", function () {
const result = utils.merge(
{},
{
headers: {
__proto__: { polluted: "nested" },
"Content-Type": "application/json",
},
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.headers["Content-Type"], "application/json");
assert.strictEqual(result.headers.hasOwnProperty("__proto__"), false);
});
it("should filter constructor key in nested objects", function () {
const result = utils.merge(
{},
{
headers: {
constructor: { prototype: { polluted: "nested" } },
"Content-Type": "application/json",
},
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.headers["Content-Type"], "application/json");
assert.strictEqual(result.headers.hasOwnProperty("constructor"), false);
});
it("should filter prototype key in nested objects", function () {
const result = utils.merge(
{},
{
headers: {
prototype: { polluted: "nested" },
"Content-Type": "application/json",
},
},
);
assert.strictEqual(result.headers["Content-Type"], "application/json");
assert.strictEqual(result.headers.hasOwnProperty("prototype"), false);
});
it("should filter dangerous keys in deeply nested objects", function () {
const result = utils.merge(
{},
{
level1: {
level2: {
__proto__: { polluted: "deep" },
prototype: { polluted: "deep" },
safe: "value",
},
},
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.level1.level2.safe, "value");
assert.strictEqual(
result.level1.level2.hasOwnProperty("__proto__"),
false,
);
});
it("should still merge regular properties correctly", function () {
const result = utils.merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 });
assert.strictEqual(result.a, 1);
assert.strictEqual(result.b.c, 2);
assert.strictEqual(result.b.d, 3);
assert.strictEqual(result.e, 4);
});
it("should handle JSON.parse payloads safely", function () {
const malicious = JSON.parse('{"__proto__": {"polluted": "yes"}}');
const result = utils.merge({}, malicious);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
});
it("should handle nested JSON.parse payloads safely", function () {
const malicious = JSON.parse(
'{"headers": {"constructor": {"prototype": {"polluted": "yes"}}}}',
);
const result = utils.merge({}, malicious);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.headers.hasOwnProperty("constructor"), false);
});
});
describe("mergeConfig", function () {
it("should filter dangerous keys at top level", function () {
const result = mergeConfig(
{},
{
__proto__: { polluted: "yes" },
constructor: { polluted: "yes" },
prototype: { polluted: "yes" },
url: "/api/test",
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.url, "/api/test");
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
assert.strictEqual(result.hasOwnProperty("constructor"), false);
assert.strictEqual(result.hasOwnProperty("prototype"), false);
});
it("should filter dangerous keys in headers", function () {
const result = mergeConfig(
{},
{
headers: {
__proto__: { polluted: "yes" },
"Content-Type": "application/json",
},
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.headers["Content-Type"], "application/json");
assert.strictEqual(result.headers.hasOwnProperty("__proto__"), false);
});
it("should filter dangerous keys in custom config properties", function () {
const result = mergeConfig(
{},
{
customProp: {
__proto__: { polluted: "yes" },
safe: "value",
},
},
);
assert.strictEqual(Object.prototype.polluted, undefined);
assert.strictEqual(result.customProp.safe, "value");
assert.strictEqual(result.customProp.hasOwnProperty("__proto__"), false);
});
it("should still merge configs correctly", function () {
const config1 = {
baseURL: "https://api.example.com",
timeout: 1000,
headers: {
common: {
Accept: "application/json",
},
},
};
const config2 = {
url: "/users",
timeout: 5000,
headers: {
common: {
"Content-Type": "application/json",
},
},
};
const result = mergeConfig(config1, config2);
assert.strictEqual(result.baseURL, "https://api.example.com");
assert.strictEqual(result.url, "/users");
assert.strictEqual(result.timeout, 5000);
assert.strictEqual(result.headers.common.Accept, "application/json");
assert.strictEqual(
result.headers.common["Content-Type"],
"application/json",
);
});
});
});