2
0
mirror of https://github.com/tenrok/axios.git synced 2026-05-15 11:59:42 +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
+22 -8
View File
@@ -1,9 +1,10 @@
'use strict'; "use strict";
import utils from '../utils.js'; import utils from "../utils.js";
import AxiosHeaders from "./AxiosHeaders.js"; import AxiosHeaders from "./AxiosHeaders.js";
const headersToObject = (thing) => thing instanceof AxiosHeaders ? { ...thing } : thing; const headersToObject = (thing) =>
thing instanceof AxiosHeaders ? { ...thing } : thing;
/** /**
* Config-specific merge-function which creates a new config-object * Config-specific merge-function which creates a new config-object
@@ -92,14 +93,27 @@ export default function mergeConfig(config1, config2) {
socketPath: defaultToConfig2, socketPath: defaultToConfig2,
responseEncoding: defaultToConfig2, responseEncoding: defaultToConfig2,
validateStatus: mergeDirectKeys, validateStatus: mergeDirectKeys,
headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) headers: (a, b, prop) =>
mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true),
}; };
utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) { utils.forEach(
const merge = mergeMap[prop] || mergeDeepProperties; Object.keys({ ...config1, ...config2 }),
function computeConfigValue(prop) {
if (
prop === "__proto__" ||
prop === "constructor" ||
prop === "prototype"
)
return;
const merge = utils.hasOwnProp(mergeMap, prop)
? mergeMap[prop]
: mergeDeepProperties;
const configValue = merge(config1[prop], config2[prop], prop); const configValue = merge(config1[prop], config2[prop], prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) ||
}); (config[prop] = configValue);
},
);
return config; return config;
} }
+179 -115
View File
@@ -1,6 +1,6 @@
'use strict'; "use strict";
import bind from './helpers/bind.js'; import bind from "./helpers/bind.js";
// utils is a library of generic helper functions non-specific to axios // utils is a library of generic helper functions non-specific to axios
@@ -8,17 +8,17 @@ const {toString} = Object.prototype;
const { getPrototypeOf } = Object; const { getPrototypeOf } = Object;
const { iterator, toStringTag } = Symbol; const { iterator, toStringTag } = Symbol;
const kindOf = (cache => thing => { const kindOf = ((cache) => (thing) => {
const str = toString.call(thing); const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
})(Object.create(null)); })(Object.create(null));
const kindOfTest = (type) => { const kindOfTest = (type) => {
type = type.toLowerCase(); type = type.toLowerCase();
return (thing) => kindOf(thing) === type return (thing) => kindOf(thing) === type;
} };
const typeOfTest = type => thing => typeof thing === type; const typeOfTest = (type) => (thing) => typeof thing === type;
/** /**
* Determine if a value is a non-null object * Determine if a value is a non-null object
@@ -36,7 +36,7 @@ const {isArray} = Array;
* *
* @returns {boolean} True if the value is undefined, otherwise false * @returns {boolean} True if the value is undefined, otherwise false
*/ */
const isUndefined = typeOfTest('undefined'); const isUndefined = typeOfTest("undefined");
/** /**
* Determine if a value is a Buffer * Determine if a value is a Buffer
@@ -46,8 +46,14 @@ const isUndefined = typeOfTest('undefined');
* @returns {boolean} True if value is a Buffer, otherwise false * @returns {boolean} True if value is a Buffer, otherwise false
*/ */
function isBuffer(val) { function isBuffer(val) {
return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) return (
&& isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val); val !== null &&
!isUndefined(val) &&
val.constructor !== null &&
!isUndefined(val.constructor) &&
isFunction(val.constructor.isBuffer) &&
val.constructor.isBuffer(val)
);
} }
/** /**
@@ -57,8 +63,7 @@ function isBuffer(val) {
* *
* @returns {boolean} True if value is an ArrayBuffer, otherwise false * @returns {boolean} True if value is an ArrayBuffer, otherwise false
*/ */
const isArrayBuffer = kindOfTest('ArrayBuffer'); const isArrayBuffer = kindOfTest("ArrayBuffer");
/** /**
* Determine if a value is a view on an ArrayBuffer * Determine if a value is a view on an ArrayBuffer
@@ -69,10 +74,10 @@ const isArrayBuffer = kindOfTest('ArrayBuffer');
*/ */
function isArrayBufferView(val) { function isArrayBufferView(val) {
let result; let result;
if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView) {
result = ArrayBuffer.isView(val); result = ArrayBuffer.isView(val);
} else { } else {
result = (val) && (val.buffer) && (isArrayBuffer(val.buffer)); result = val && val.buffer && isArrayBuffer(val.buffer);
} }
return result; return result;
} }
@@ -84,7 +89,7 @@ function isArrayBufferView(val) {
* *
* @returns {boolean} True if value is a String, otherwise false * @returns {boolean} True if value is a String, otherwise false
*/ */
const isString = typeOfTest('string'); const isString = typeOfTest("string");
/** /**
* Determine if a value is a Function * Determine if a value is a Function
@@ -92,7 +97,7 @@ const isString = typeOfTest('string');
* @param {*} val The value to test * @param {*} val The value to test
* @returns {boolean} True if value is a Function, otherwise false * @returns {boolean} True if value is a Function, otherwise false
*/ */
const isFunction = typeOfTest('function'); const isFunction = typeOfTest("function");
/** /**
* Determine if a value is a Number * Determine if a value is a Number
@@ -101,7 +106,7 @@ const isFunction = typeOfTest('function');
* *
* @returns {boolean} True if value is a Number, otherwise false * @returns {boolean} True if value is a Number, otherwise false
*/ */
const isNumber = typeOfTest('number'); const isNumber = typeOfTest("number");
/** /**
* Determine if a value is an Object * Determine if a value is an Object
@@ -110,7 +115,7 @@ const isNumber = typeOfTest('number');
* *
* @returns {boolean} True if value is an Object, otherwise false * @returns {boolean} True if value is an Object, otherwise false
*/ */
const isObject = (thing) => thing !== null && typeof thing === 'object'; const isObject = (thing) => thing !== null && typeof thing === "object";
/** /**
* Determine if a value is a Boolean * Determine if a value is a Boolean
@@ -118,7 +123,7 @@ const isObject = (thing) => thing !== null && typeof thing === 'object';
* @param {*} thing The value to test * @param {*} thing The value to test
* @returns {boolean} True if value is a Boolean, otherwise false * @returns {boolean} True if value is a Boolean, otherwise false
*/ */
const isBoolean = thing => thing === true || thing === false; const isBoolean = (thing) => thing === true || thing === false;
/** /**
* Determine if a value is a plain Object * Determine if a value is a plain Object
@@ -128,13 +133,19 @@ const isBoolean = thing => thing === true || thing === false;
* @returns {boolean} True if value is a plain Object, otherwise false * @returns {boolean} True if value is a plain Object, otherwise false
*/ */
const isPlainObject = (val) => { const isPlainObject = (val) => {
if (kindOf(val) !== 'object') { if (kindOf(val) !== "object") {
return false; return false;
} }
const prototype = getPrototypeOf(val); const prototype = getPrototypeOf(val);
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(toStringTag in val) && !(iterator in val); return (
} (prototype === null ||
prototype === Object.prototype ||
Object.getPrototypeOf(prototype) === null) &&
!(toStringTag in val) &&
!(iterator in val)
);
};
/** /**
* Determine if a value is an empty object (safely handles Buffers) * Determine if a value is an empty object (safely handles Buffers)
@@ -150,12 +161,15 @@ const isEmptyObject = (val) => {
} }
try { try {
return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype; return (
Object.keys(val).length === 0 &&
Object.getPrototypeOf(val) === Object.prototype
);
} catch (e) { } catch (e) {
// Fallback for any other objects that might cause RangeError with Object.keys() // Fallback for any other objects that might cause RangeError with Object.keys()
return false; return false;
} }
} };
/** /**
* Determine if a value is a Date * Determine if a value is a Date
@@ -164,7 +178,7 @@ const isEmptyObject = (val) => {
* *
* @returns {boolean} True if value is a Date, otherwise false * @returns {boolean} True if value is a Date, otherwise false
*/ */
const isDate = kindOfTest('Date'); const isDate = kindOfTest("Date");
/** /**
* Determine if a value is a File * Determine if a value is a File
@@ -173,7 +187,7 @@ const isDate = kindOfTest('Date');
* *
* @returns {boolean} True if value is a File, otherwise false * @returns {boolean} True if value is a File, otherwise false
*/ */
const isFile = kindOfTest('File'); const isFile = kindOfTest("File");
/** /**
* Determine if a value is a Blob * Determine if a value is a Blob
@@ -182,7 +196,7 @@ const isFile = kindOfTest('File');
* *
* @returns {boolean} True if value is a Blob, otherwise false * @returns {boolean} True if value is a Blob, otherwise false
*/ */
const isBlob = kindOfTest('Blob'); const isBlob = kindOfTest("Blob");
/** /**
* Determine if a value is a FileList * Determine if a value is a FileList
@@ -191,7 +205,7 @@ const isBlob = kindOfTest('Blob');
* *
* @returns {boolean} True if value is a File, otherwise false * @returns {boolean} True if value is a File, otherwise false
*/ */
const isFileList = kindOfTest('FileList'); const isFileList = kindOfTest("FileList");
/** /**
* Determine if a value is a Stream * Determine if a value is a Stream
@@ -211,16 +225,17 @@ const isStream = (val) => isObject(val) && isFunction(val.pipe);
*/ */
const isFormData = (thing) => { const isFormData = (thing) => {
let kind; let kind;
return thing && ( return (
(typeof FormData === 'function' && thing instanceof FormData) || ( thing &&
isFunction(thing.append) && ( ((typeof FormData === "function" && thing instanceof FormData) ||
(kind = kindOf(thing)) === 'formdata' || (isFunction(thing.append) &&
((kind = kindOf(thing)) === "formdata" ||
// detect form-data instance // detect form-data instance
(kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]') (kind === "object" &&
) isFunction(thing.toString) &&
) thing.toString() === "[object FormData]"))))
) );
} };
/** /**
* Determine if a value is a URLSearchParams object * Determine if a value is a URLSearchParams object
@@ -229,9 +244,14 @@ const isFormData = (thing) => {
* *
* @returns {boolean} True if value is a URLSearchParams object, otherwise false * @returns {boolean} True if value is a URLSearchParams object, otherwise false
*/ */
const isURLSearchParams = kindOfTest('URLSearchParams'); const isURLSearchParams = kindOfTest("URLSearchParams");
const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream', 'Request', 'Response', 'Headers'].map(kindOfTest); const [isReadableStream, isRequest, isResponse, isHeaders] = [
"ReadableStream",
"Request",
"Response",
"Headers",
].map(kindOfTest);
/** /**
* Trim excess whitespace off the beginning and end of a string * Trim excess whitespace off the beginning and end of a string
@@ -240,8 +260,8 @@ const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream',
* *
* @returns {String} The String freed of excess whitespace * @returns {String} The String freed of excess whitespace
*/ */
const trim = (str) => str.trim ? const trim = (str) =>
str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
/** /**
* Iterate over an Array or an Object invoking a function for each item. * Iterate over an Array or an Object invoking a function for each item.
@@ -261,7 +281,7 @@ const trim = (str) => str.trim ?
*/ */
function forEach(obj, fn, { allOwnKeys = false } = {}) { function forEach(obj, fn, { allOwnKeys = false } = {}) {
// Don't bother if no value provided // Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') { if (obj === null || typeof obj === "undefined") {
return; return;
} }
@@ -269,7 +289,7 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
let l; let l;
// Force an array if not already something iterable // Force an array if not already something iterable
if (typeof obj !== 'object') { if (typeof obj !== "object") {
/*eslint no-param-reassign:0*/ /*eslint no-param-reassign:0*/
obj = [obj]; obj = [obj];
} }
@@ -286,7 +306,9 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
} }
// Iterate over object keys // Iterate over object keys
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj); const keys = allOwnKeys
? Object.getOwnPropertyNames(obj)
: Object.keys(obj);
const len = keys.length; const len = keys.length;
let key; let key;
@@ -318,10 +340,15 @@ function findKey(obj, key) {
const _global = (() => { const _global = (() => {
/*eslint no-undef:0*/ /*eslint no-undef:0*/
if (typeof globalThis !== "undefined") return globalThis; if (typeof globalThis !== "undefined") return globalThis;
return typeof self !== "undefined" ? self : (typeof window !== 'undefined' ? window : global) return typeof self !== "undefined"
? self
: typeof window !== "undefined"
? window
: global;
})(); })();
const isContextDefined = (context) => !isUndefined(context) && context !== _global; const isContextDefined = (context) =>
!isUndefined(context) && context !== _global;
/** /**
* Accepts varargs expecting each argument to be an object, then * Accepts varargs expecting each argument to be an object, then
@@ -342,10 +369,15 @@ const isContextDefined = (context) => !isUndefined(context) && context !== _glob
* @returns {Object} Result of all merge properties * @returns {Object} Result of all merge properties
*/ */
function merge(/* obj1, obj2, obj3, ... */) { function merge(/* obj1, obj2, obj3, ... */) {
const {caseless, skipUndefined} = isContextDefined(this) && this || {}; const { caseless, skipUndefined } = (isContextDefined(this) && this) || {};
const result = {}; const result = {};
const assignValue = (val, key) => { const assignValue = (val, key) => {
const targetKey = caseless && findKey(result, key) || key; // Skip dangerous property names to prevent prototype pollution
if (key === "__proto__" || key === "constructor" || key === "prototype") {
return;
}
const targetKey = (caseless && findKey(result, key)) || key;
if (isPlainObject(result[targetKey]) && isPlainObject(val)) { if (isPlainObject(result[targetKey]) && isPlainObject(val)) {
result[targetKey] = merge(result[targetKey], val); result[targetKey] = merge(result[targetKey], val);
} else if (isPlainObject(val)) { } else if (isPlainObject(val)) {
@@ -355,7 +387,7 @@ function merge(/* obj1, obj2, obj3, ... */) {
} else if (!skipUndefined || !isUndefined(val)) { } else if (!skipUndefined || !isUndefined(val)) {
result[targetKey] = val; result[targetKey] = val;
} }
} };
for (let i = 0, l = arguments.length; i < l; i++) { for (let i = 0, l = arguments.length; i < l; i++) {
arguments[i] && forEach(arguments[i], assignValue); arguments[i] && forEach(arguments[i], assignValue);
@@ -375,25 +407,29 @@ function merge(/* obj1, obj2, obj3, ... */) {
* @returns {Object} The resulting value of object a * @returns {Object} The resulting value of object a
*/ */
const extend = (a, b, thisArg, { allOwnKeys } = {}) => { const extend = (a, b, thisArg, { allOwnKeys } = {}) => {
forEach(b, (val, key) => { forEach(
b,
(val, key) => {
if (thisArg && isFunction(val)) { if (thisArg && isFunction(val)) {
Object.defineProperty(a, key, { Object.defineProperty(a, key, {
value: bind(val, thisArg), value: bind(val, thisArg),
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}); });
} else { } else {
Object.defineProperty(a, key, { Object.defineProperty(a, key, {
value: val, value: val,
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}); });
} }
}, {allOwnKeys}); },
{ allOwnKeys },
);
return a; return a;
} };
/** /**
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
@@ -403,11 +439,11 @@ const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
* @returns {string} content value without BOM * @returns {string} content value without BOM
*/ */
const stripBOM = (content) => { const stripBOM = (content) => {
if (content.charCodeAt(0) === 0xFEFF) { if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1); content = content.slice(1);
} }
return content; return content;
} };
/** /**
* Inherit the prototype methods from one constructor into another * Inherit the prototype methods from one constructor into another
@@ -419,18 +455,21 @@ const stripBOM = (content) => {
* @returns {void} * @returns {void}
*/ */
const inherits = (constructor, superConstructor, props, descriptors) => { const inherits = (constructor, superConstructor, props, descriptors) => {
constructor.prototype = Object.create(superConstructor.prototype, descriptors); constructor.prototype = Object.create(
Object.defineProperty(constructor.prototype, 'constructor', { superConstructor.prototype,
descriptors,
);
Object.defineProperty(constructor.prototype, "constructor", {
value: constructor, value: constructor,
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true configurable: true,
}); });
Object.defineProperty(constructor, 'super', { Object.defineProperty(constructor, "super", {
value: superConstructor.prototype value: superConstructor.prototype,
}); });
props && Object.assign(constructor.prototype, props); props && Object.assign(constructor.prototype, props);
} };
/** /**
* Resolve object with deep prototype chain to a flat object * Resolve object with deep prototype chain to a flat object
@@ -456,16 +495,23 @@ const toFlatObject = (sourceObj, destObj, filter, propFilter) => {
i = props.length; i = props.length;
while (i-- > 0) { while (i-- > 0) {
prop = props[i]; prop = props[i];
if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) { if (
(!propFilter || propFilter(prop, sourceObj, destObj)) &&
!merged[prop]
) {
destObj[prop] = sourceObj[prop]; destObj[prop] = sourceObj[prop];
merged[prop] = true; merged[prop] = true;
} }
} }
sourceObj = filter !== false && getPrototypeOf(sourceObj); sourceObj = filter !== false && getPrototypeOf(sourceObj);
} while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype); } while (
sourceObj &&
(!filter || filter(sourceObj, destObj)) &&
sourceObj !== Object.prototype
);
return destObj; return destObj;
} };
/** /**
* Determines whether a string ends with the characters of a specified string * Determines whether a string ends with the characters of a specified string
@@ -484,8 +530,7 @@ const endsWith = (str, searchString, position) => {
position -= searchString.length; position -= searchString.length;
const lastIndex = str.indexOf(searchString, position); const lastIndex = str.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position; return lastIndex !== -1 && lastIndex === position;
} };
/** /**
* Returns new array from array like object or null if failed * Returns new array from array like object or null if failed
@@ -504,7 +549,7 @@ const toArray = (thing) => {
arr[i] = thing[i]; arr[i] = thing[i];
} }
return arr; return arr;
} };
/** /**
* Checking if the Uint8Array exists and if it does, it returns a function that checks if the * Checking if the Uint8Array exists and if it does, it returns a function that checks if the
@@ -515,12 +560,12 @@ const toArray = (thing) => {
* @returns {Array} * @returns {Array}
*/ */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
const isTypedArray = (TypedArray => { const isTypedArray = ((TypedArray) => {
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
return thing => { return (thing) => {
return TypedArray && thing instanceof TypedArray; return TypedArray && thing instanceof TypedArray;
}; };
})(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array)); })(typeof Uint8Array !== "undefined" && getPrototypeOf(Uint8Array));
/** /**
* For each entry in the object, call the function with the key and value. * For each entry in the object, call the function with the key and value.
@@ -541,7 +586,7 @@ const forEachEntry = (obj, fn) => {
const pair = result.value; const pair = result.value;
fn.call(obj, pair[0], pair[1]); fn.call(obj, pair[0], pair[1]);
} }
} };
/** /**
* It takes a regular expression and a string, and returns an array of all the matches * It takes a regular expression and a string, and returns an array of all the matches
@@ -560,21 +605,25 @@ const matchAll = (regExp, str) => {
} }
return arr; return arr;
} };
/* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */ /* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */
const isHTMLForm = kindOfTest('HTMLFormElement'); const isHTMLForm = kindOfTest("HTMLFormElement");
const toCamelCase = str => { const toCamelCase = (str) => {
return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, return str
function replacer(m, p1, p2) { .toLowerCase()
.replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) {
return p1.toUpperCase() + p2; return p1.toUpperCase() + p2;
} });
);
}; };
/* Creating a function that will check if an object has a property. */ /* Creating a function that will check if an object has a property. */
const hasOwnProperty = (({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call(obj, prop))(Object.prototype); const hasOwnProperty = (
({ hasOwnProperty }) =>
(obj, prop) =>
hasOwnProperty.call(obj, prop)
)(Object.prototype);
/** /**
* Determine if a value is a RegExp object * Determine if a value is a RegExp object
@@ -583,7 +632,7 @@ const hasOwnProperty = (({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call
* *
* @returns {boolean} True if value is a RegExp object, otherwise false * @returns {boolean} True if value is a RegExp object, otherwise false
*/ */
const isRegExp = kindOfTest('RegExp'); const isRegExp = kindOfTest("RegExp");
const reduceDescriptors = (obj, reducer) => { const reduceDescriptors = (obj, reducer) => {
const descriptors = Object.getOwnPropertyDescriptors(obj); const descriptors = Object.getOwnPropertyDescriptors(obj);
@@ -597,7 +646,7 @@ const reduceDescriptors = (obj, reducer) => {
}); });
Object.defineProperties(obj, reducedDescriptors); Object.defineProperties(obj, reducedDescriptors);
} };
/** /**
* Makes all methods read-only * Makes all methods read-only
@@ -607,7 +656,10 @@ const reduceDescriptors = (obj, reducer) => {
const freezeMethods = (obj) => { const freezeMethods = (obj) => {
reduceDescriptors(obj, (descriptor, name) => { reduceDescriptors(obj, (descriptor, name) => {
// skip restricted props in strict mode // skip restricted props in strict mode
if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { if (
isFunction(obj) &&
["arguments", "caller", "callee"].indexOf(name) !== -1
) {
return false; return false;
} }
@@ -617,40 +669,42 @@ const freezeMethods = (obj) => {
descriptor.enumerable = false; descriptor.enumerable = false;
if ('writable' in descriptor) { if ("writable" in descriptor) {
descriptor.writable = false; descriptor.writable = false;
return; return;
} }
if (!descriptor.set) { if (!descriptor.set) {
descriptor.set = () => { descriptor.set = () => {
throw Error('Can not rewrite read-only method \'' + name + '\''); throw Error("Can not rewrite read-only method '" + name + "'");
}; };
} }
}); });
} };
const toObjectSet = (arrayOrString, delimiter) => { const toObjectSet = (arrayOrString, delimiter) => {
const obj = {}; const obj = {};
const define = (arr) => { const define = (arr) => {
arr.forEach(value => { arr.forEach((value) => {
obj[value] = true; obj[value] = true;
}); });
} };
isArray(arrayOrString) ? define(arrayOrString) : define(String(arrayOrString).split(delimiter)); isArray(arrayOrString)
? define(arrayOrString)
: define(String(arrayOrString).split(delimiter));
return obj; return obj;
} };
const noop = () => {} const noop = () => {};
const toFiniteNumber = (value, defaultValue) => { const toFiniteNumber = (value, defaultValue) => {
return value != null && Number.isFinite(value = +value) ? value : defaultValue; return value != null && Number.isFinite((value = +value))
} ? value
: defaultValue;
};
/** /**
* If the thing is a FormData object, return true, otherwise return false. * If the thing is a FormData object, return true, otherwise return false.
@@ -660,14 +714,18 @@ const toFiniteNumber = (value, defaultValue) => {
* @returns {boolean} * @returns {boolean}
*/ */
function isSpecCompliantForm(thing) { function isSpecCompliantForm(thing) {
return !!(thing && isFunction(thing.append) && thing[toStringTag] === 'FormData' && thing[iterator]); return !!(
thing &&
isFunction(thing.append) &&
thing[toStringTag] === "FormData" &&
thing[iterator]
);
} }
const toJSONObject = (obj) => { const toJSONObject = (obj) => {
const stack = new Array(10); const stack = new Array(10);
const visit = (source, i) => { const visit = (source, i) => {
if (isObject(source)) { if (isObject(source)) {
if (stack.indexOf(source) >= 0) { if (stack.indexOf(source) >= 0) {
return; return;
@@ -678,7 +736,7 @@ const toJSONObject = (obj) => {
return source; return source;
} }
if(!('toJSON' in source)) { if (!("toJSON" in source)) {
stack[i] = source; stack[i] = source;
const target = isArray(source) ? [] : {}; const target = isArray(source) ? [] : {};
@@ -694,15 +752,18 @@ const toJSONObject = (obj) => {
} }
return source; return source;
} };
return visit(obj, 0); return visit(obj, 0);
} };
const isAsyncFn = kindOfTest('AsyncFunction'); const isAsyncFn = kindOfTest("AsyncFunction");
const isThenable = (thing) => const isThenable = (thing) =>
thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); thing &&
(isObject(thing) || isFunction(thing)) &&
isFunction(thing.then) &&
isFunction(thing.catch);
// original code // original code
// https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34 // https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34
@@ -712,32 +773,35 @@ const _setImmediate = ((setImmediateSupported, postMessageSupported) => {
return setImmediate; return setImmediate;
} }
return postMessageSupported ? ((token, callbacks) => { return postMessageSupported
_global.addEventListener("message", ({source, data}) => { ? ((token, callbacks) => {
_global.addEventListener(
"message",
({ source, data }) => {
if (source === _global && data === token) { if (source === _global && data === token) {
callbacks.length && callbacks.shift()(); callbacks.length && callbacks.shift()();
} }
}, false); },
false,
);
return (cb) => { return (cb) => {
callbacks.push(cb); callbacks.push(cb);
_global.postMessage(token, "*"); _global.postMessage(token, "*");
} };
})(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb); })(`axios@${Math.random()}`, [])
})( : (cb) => setTimeout(cb);
typeof setImmediate === 'function', })(typeof setImmediate === "function", isFunction(_global.postMessage));
isFunction(_global.postMessage)
);
const asap = typeof queueMicrotask !== 'undefined' ? const asap =
queueMicrotask.bind(_global) : ( typeof process !== 'undefined' && process.nextTick || _setImmediate); typeof queueMicrotask !== "undefined"
? queueMicrotask.bind(_global)
: (typeof process !== "undefined" && process.nextTick) || _setImmediate;
// ********************* // *********************
const isIterable = (thing) => thing != null && isFunction(thing[iterator]); const isIterable = (thing) => thing != null && isFunction(thing[iterator]);
export default { export default {
isArray, isArray,
isArrayBuffer, isArrayBuffer,
@@ -795,5 +859,5 @@ export default {
isThenable, isThenable,
setImmediate: _setImmediate, setImmediate: _setImmediate,
asap, asap,
isIterable isIterable,
}; };
+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",
);
});
});
});