diff --git a/dist/index.js b/dist/index.js index 8b40c64..75d6bc3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -97593,7 +97593,7 @@ __webpack_unused_export__ = defaultContentType /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -/*! Axios v1.15.0 Copyright (c) 2026 Matt Zabriskie and contributors */ +/*! Axios v1.15.2 Copyright (c) 2026 Matt Zabriskie and contributors */ var FormData$1 = __nccwpck_require__(91403); @@ -97603,6 +97603,7 @@ var http = __nccwpck_require__(13685); var https = __nccwpck_require__(95687); var http2 = __nccwpck_require__(85158); var util = __nccwpck_require__(73837); +var path = __nccwpck_require__(71017); var followRedirects = __nccwpck_require__(67707); var zlib = __nccwpck_require__(59796); var stream = __nccwpck_require__(12781); @@ -97865,10 +97866,16 @@ function getGlobal() { const G = getGlobal(); const FormDataCtor = typeof G.FormData !== 'undefined' ? G.FormData : undefined; const isFormData = thing => { - let kind; - return thing && (FormDataCtor && thing instanceof FormDataCtor || isFunction$1(thing.append) && ((kind = kindOf(thing)) === 'formdata' || + if (!thing) return false; + if (FormDataCtor && thing instanceof FormDataCtor) return true; + // Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData (GHSA-6chq-wfr3-2hj9). + const proto = getPrototypeOf(thing); + if (!proto || proto === Object.prototype) return false; + if (!isFunction$1(thing.append)) return false; + const kind = kindOf(thing); + return kind === 'formdata' || // detect form-data instance - kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]')); + kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]'; }; /** @@ -98545,6 +98552,7 @@ AxiosError.ERR_BAD_REQUEST = 'ERR_BAD_REQUEST'; AxiosError.ERR_CANCELED = 'ERR_CANCELED'; AxiosError.ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT'; AxiosError.ERR_INVALID_URL = 'ERR_INVALID_URL'; +AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED'; /** * Determines if the given thing is a array or js object. @@ -98646,6 +98654,7 @@ function toFormData(obj, formData, options) { const dots = options.dots; const indexes = options.indexes; const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; + const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth; const useBlob = _Blob && utils$1.isSpecCompliantForm(formData); if (!utils$1.isFunction(visitor)) { throw new TypeError('visitor must be a function'); @@ -98712,8 +98721,11 @@ function toFormData(obj, formData, options) { convertValue, isVisitable }); - function build(value, path) { + function build(value, path, depth = 0) { if (utils$1.isUndefined(value)) return; + if (depth > maxDepth) { + throw new AxiosError('Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED); + } if (stack.indexOf(value) !== -1) { throw Error('Circular reference detected in ' + path.join('.')); } @@ -98721,7 +98733,7 @@ function toFormData(obj, formData, options) { utils$1.forEach(value, function each(el, key) { const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path, exposedHelpers); if (result === true) { - build(el, path ? path.concat(key) : [key]); + build(el, path ? path.concat(key) : [key], depth + 1); } }); stack.pop(); @@ -98748,10 +98760,9 @@ function encode$1(str) { '(': '%28', ')': '%29', '~': '%7E', - '%20': '+', - '%00': '\x00' + '%20': '+' }; - return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) { + return encodeURIComponent(str).replace(/[!'()~]|%20/g, function replacer(match) { return charMap[match]; }); } @@ -99052,7 +99063,7 @@ function formDataToJSON(formData) { name = !name && utils$1.isArray(target) ? target.length : name; if (isLast) { if (utils$1.hasOwnProp(target, name)) { - target[name] = [target[name], value]; + target[name] = utils$1.isArray(target[name]) ? target[name].concat(value) : [target[name], value]; } else { target[name] = value; } @@ -99077,6 +99088,8 @@ function formDataToJSON(formData) { return null; } +const own = (obj, key) => obj != null && utils$1.hasOwnProp(obj, key) ? obj[key] : undefined; + /** * It takes a string, tries to parse it, and if it fails, it returns the stringified version * of the input @@ -99126,14 +99139,16 @@ const defaults = { } let isFileList; if (isObjectPayload) { + const formSerializer = own(this, 'formSerializer'); if (contentType.indexOf('application/x-www-form-urlencoded') > -1) { - return toURLEncodedForm(data, this.formSerializer).toString(); + return toURLEncodedForm(data, formSerializer).toString(); } if ((isFileList = utils$1.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) { - const _FormData = this.env && this.env.FormData; + const env = own(this, 'env'); + const _FormData = env && env.FormData; return toFormData(isFileList ? { 'files[]': data - } : data, _FormData && new _FormData(), this.formSerializer); + } : data, _FormData && new _FormData(), formSerializer); } } if (isObjectPayload || hasJSONContentType) { @@ -99143,21 +99158,22 @@ const defaults = { return data; }], transformResponse: [function transformResponse(data) { - const transitional = this.transitional || defaults.transitional; + const transitional = own(this, 'transitional') || defaults.transitional; const forcedJSONParsing = transitional && transitional.forcedJSONParsing; - const JSONRequested = this.responseType === 'json'; + const responseType = own(this, 'responseType'); + const JSONRequested = responseType === 'json'; if (utils$1.isResponse(data) || utils$1.isReadableStream(data)) { return data; } - if (data && utils$1.isString(data) && (forcedJSONParsing && !this.responseType || JSONRequested)) { + if (data && utils$1.isString(data) && (forcedJSONParsing && !responseType || JSONRequested)) { const silentJSONParsing = transitional && transitional.silentJSONParsing; const strictJSONParsing = !silentJSONParsing && JSONRequested; try { - return JSON.parse(data, this.parseReviver); + return JSON.parse(data, own(this, 'parseReviver')); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { - throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response); + throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, own(this, 'response')); } throw e; } @@ -99236,38 +99252,37 @@ var parseHeaders = rawHeaders => { }; const $internals = Symbol('internals'); -const isValidHeaderValue = value => !/[\r\n]/.test(value); -function assertValidHeaderValue(value, header) { - if (value === false || value == null) { - return; - } - if (utils$1.isArray(value)) { - value.forEach(v => assertValidHeaderValue(v, header)); - return; +const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g; +function trimSPorHTAB(str) { + let start = 0; + let end = str.length; + while (start < end) { + const code = str.charCodeAt(start); + if (code !== 0x09 && code !== 0x20) { + break; + } + start += 1; } - if (!isValidHeaderValue(String(value))) { - throw new Error(`Invalid character in header content ["${header}"]`); + while (end > start) { + const code = str.charCodeAt(end - 1); + if (code !== 0x09 && code !== 0x20) { + break; + } + end -= 1; } + return start === 0 && end === str.length ? str : str.slice(start, end); } function normalizeHeader(header) { return header && String(header).trim().toLowerCase(); } -function stripTrailingCRLF(str) { - let end = str.length; - while (end > 0) { - const charCode = str.charCodeAt(end - 1); - if (charCode !== 10 && charCode !== 13) { - break; - } - end -= 1; - } - return end === str.length ? str : str.slice(0, end); +function sanitizeHeaderValue(str) { + return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, '')); } function normalizeValue(value) { if (value === false || value == null) { return value; } - return utils$1.isArray(value) ? value.map(normalizeValue) : stripTrailingCRLF(String(value)); + return utils$1.isArray(value) ? value.map(normalizeValue) : sanitizeHeaderValue(String(value)); } function parseTokens(str) { const tokens = Object.create(null); @@ -99323,7 +99338,6 @@ class AxiosHeaders { } const key = utils$1.findKey(self, lHeader); if (!key || self[key] === undefined || _rewrite === true || _rewrite === undefined && self[key] !== false) { - assertValidHeaderValue(_value, _header); self[key || _header] = normalizeValue(_value); } } @@ -99593,7 +99607,7 @@ function combineURLs(baseURL, relativeURL) { */ function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { let isRelativeUrl = !isAbsoluteURL(requestedURL); - if (baseURL && (isRelativeUrl || allowAbsoluteUrls == false)) { + if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) { return combineURLs(baseURL, requestedURL); } return requestedURL; @@ -99695,7 +99709,7 @@ function getEnv(key) { return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || ''; } -const VERSION = "1.15.0"; +const VERSION = "1.15.2"; function parseProtocol(url) { const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); @@ -99886,7 +99900,8 @@ class FormDataPart { if (isStringValue) { value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF)); } else { - headers += `Content-Type: ${value.type || 'application/octet-stream'}${CRLF}`; + const safeType = String(value.type || 'application/octet-stream').replace(/[\r\n]/g, ''); + headers += `Content-Type: ${safeType}${CRLF}`; } this.headers = textEncoder.encode(headers + CRLF); this.contentLength = isStringValue ? value.byteLength : value.size; @@ -99987,6 +100002,47 @@ const callbackify = (fn, reducer) => { } : fn; }; +const LOOPBACK_HOSTNAMES = new Set(['localhost']); +const isIPv4Loopback = host => { + const parts = host.split('.'); + if (parts.length !== 4) return false; + if (parts[0] !== '127') return false; + return parts.every(p => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255); +}; +const isIPv6Loopback = host => { + // Collapse all-zero groups: any form of ::1 / 0:0:...:0:1 + // First, strip any leading "::" by normalising with Set lookup of common forms, + // then fall back to structural check. + if (host === '::1') return true; + + // Check IPv4-mapped IPv6 loopback: ::ffff: or ::ffff: + // Node's URL parser normalises ::ffff:127.0.0.1 → ::ffff:7f00:1 + const v4MappedDotted = host.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i); + if (v4MappedDotted) return isIPv4Loopback(v4MappedDotted[1]); + const v4MappedHex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i); + if (v4MappedHex) { + const high = parseInt(v4MappedHex[1], 16); + // High 16 bits must start with 127 (0x7f) — i.e. 0x7f00..0x7fff + return high >= 0x7f00 && high <= 0x7fff; + } + + // Full-form ::1 variants: any number of zero groups followed by trailing 1 + // e.g. 0:0:0:0:0:0:0:1, 0000:...:0001 + const groups = host.split(':'); + if (groups.length === 8) { + for (let i = 0; i < 7; i++) { + if (!/^0+$/.test(groups[i])) return false; + } + return /^0*1$/.test(groups[7]); + } + return false; +}; +const isLoopback = host => { + if (!host) return false; + if (LOOPBACK_HOSTNAMES.has(host)) return true; + if (isIPv4Loopback(host)) return true; + return isIPv6Loopback(host); +}; const DEFAULT_PORTS = { http: 80, https: 443, @@ -100059,7 +100115,7 @@ function shouldBypassProxy(location) { if (entryHost.charAt(0) === '.') { return hostname.endsWith(entryHost); } - return hostname === entryHost; + return hostname === entryHost || isLoopback(hostname) && isLoopback(entryHost); }); } @@ -100146,19 +100202,19 @@ const progressEventReducer = (listener, isDownloadStream, freq = 3) => { let bytesNotified = 0; const _speedometer = speedometer(50, 250); return throttle(e => { - const loaded = e.loaded; + const rawLoaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; - const progressBytes = loaded - bytesNotified; + const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded; + const progressBytes = Math.max(0, loaded - bytesNotified); const rate = _speedometer(progressBytes); - const inRange = loaded <= total; - bytesNotified = loaded; + bytesNotified = Math.max(bytesNotified, loaded); const data = { loaded, total, progress: total ? loaded / total : undefined, bytes: progressBytes, rate: rate ? rate : undefined, - estimated: rate && total && inRange ? (total - loaded) / rate : undefined, + estimated: rate && total ? (total - loaded) / rate : undefined, event: e, lengthComputable: total != null, [isDownloadStream ? 'download' : 'upload']: true @@ -100253,6 +100309,11 @@ const { https: httpsFollow } = followRedirects; const isHttps = /https:?/; + +// Symbols used to bind a single 'error' listener to a pooled socket and track +// the request currently owning that socket across keep-alive reuse (issue #10780). +const kAxiosSocketListener = Symbol('axios.http.socketListener'); +const kAxiosCurrentReq = Symbol('axios.http.currentReq'); const supportedProtocols = platform.protocols.map(protocol => { return protocol + ':'; }); @@ -100486,17 +100547,15 @@ const http2Transport = { /*eslint consistent-return:0*/ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { - let { - data, - lookup, - family, - httpVersion = 1, - http2Options - } = config; - const { - responseType, - responseEncoding - } = config; + const own = key => utils$1.hasOwnProp(config, key) ? config[key] : undefined; + let data = own('data'); + let lookup = own('lookup'); + let family = own('family'); + let httpVersion = own('httpVersion'); + if (httpVersion === undefined) httpVersion = 1; + let http2Options = own('http2Options'); + const responseType = own('responseType'); + const responseEncoding = own('responseEncoding'); const method = config.method.toUpperCase(); let isDone; let rejected = false; @@ -100640,7 +100699,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { boundary: userBoundary && userBoundary[1] || undefined }); // support for https://www.npmjs.com/package/form-data api - } else if (utils$1.isFormData(data) && utils$1.isFunction(data.getHeaders)) { + } else if (utils$1.isFormData(data) && utils$1.isFunction(data.getHeaders) && data.getHeaders !== Object.prototype.getHeaders) { headers.set(data.getHeaders()); if (!headers.hasContentLength()) { try { @@ -100689,9 +100748,10 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // HTTP basic authentication let auth = undefined; - if (config.auth) { - const username = config.auth.username || ''; - const password = config.auth.password || ''; + const configAuth = own('auth'); + if (configAuth) { + const username = configAuth.username || ''; + const password = configAuth.password || ''; auth = username + ':' + password; } if (!auth && parsed.username) { @@ -100700,9 +100760,9 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { auth = urlUsername + ':' + urlPassword; } auth && headers.delete('authorization'); - let path; + let path$1; try { - path = buildURL(parsed.pathname + parsed.search, config.params, config.paramsSerializer).replace(/^\?/, ''); + path$1 = buildURL(parsed.pathname + parsed.search, config.params, config.paramsSerializer).replace(/^\?/, ''); } catch (err) { const customErr = new Error(err.message); customErr.config = config; @@ -100711,8 +100771,12 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { return reject(customErr); } headers.set('Accept-Encoding', 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''), false); - const options = { - path, + + // Null-prototype to block prototype pollution gadgets on properties read + // directly by Node's http.request (e.g. insecureHTTPParser, lookup). + // See GHSA-q8qp-cvcw-x6jj. + const options = Object.assign(Object.create(null), { + path: path$1, method: method, headers: headers.toJSON(), agents: { @@ -100723,13 +100787,24 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { protocol, family, beforeRedirect: dispatchBeforeRedirect, - beforeRedirects: {}, + beforeRedirects: Object.create(null), http2Options - }; + }); // cacheable-lookup integration hotfix !utils$1.isUndefined(lookup) && (options.lookup = lookup); if (config.socketPath) { + if (typeof config.socketPath !== 'string') { + return reject(new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)); + } + if (config.allowedSocketPaths != null) { + const allowed = Array.isArray(config.allowedSocketPaths) ? config.allowedSocketPaths : [config.allowedSocketPaths]; + const resolvedSocket = path.resolve(config.socketPath); + const isAllowed = allowed.some(entry => typeof entry === 'string' && path.resolve(entry) === resolvedSocket); + if (!isAllowed) { + return reject(new AxiosError(`socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`, AxiosError.ERR_BAD_OPTION_VALUE, config)); + } + } options.socketPath = config.socketPath; } else { options.hostname = parsed.hostname.startsWith('[') ? parsed.hostname.slice(1, -1) : parsed.hostname; @@ -100742,16 +100817,18 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { if (isHttp2) { transport = http2Transport; } else { - if (config.transport) { - transport = config.transport; + const configTransport = own('transport'); + if (configTransport) { + transport = configTransport; } else if (config.maxRedirects === 0) { transport = isHttpsRequest ? https : http; } else { if (config.maxRedirects) { options.maxRedirects = config.maxRedirects; } - if (config.beforeRedirect) { - options.beforeRedirects.config = config.beforeRedirect; + const configBeforeRedirect = own('beforeRedirect'); + if (configBeforeRedirect) { + options.beforeRedirects.config = configBeforeRedirect; } transport = isHttpsRequest ? httpsFollow : httpFollow; } @@ -100762,9 +100839,11 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited options.maxBodyLength = Infinity; } - if (config.insecureHTTPParser) { - options.insecureHTTPParser = config.insecureHTTPParser; - } + + // Always set an explicit own value so a polluted + // Object.prototype.insecureHTTPParser cannot enable the lenient parser + // through Node's internal options copy (GHSA-q8qp-cvcw-x6jj). + options.insecureHTTPParser = Boolean(own('insecureHTTPParser')); // Create the request req = transport.request(options, function handleResponse(res) { @@ -100829,6 +100908,25 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { request: lastRequest }; if (responseType === 'stream') { + // Enforce maxContentLength on streamed responses; previously this + // was applied only to buffered responses. See GHSA-vf2m-468p-8v99. + if (config.maxContentLength > -1) { + const limit = config.maxContentLength; + const source = responseStream; + async function* enforceMaxContentLength() { + let totalResponseBytes = 0; + for await (const chunk of source) { + totalResponseBytes += chunk.length; + if (totalResponseBytes > limit) { + throw new AxiosError('maxContentLength size of ' + limit + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, lastRequest); + } + yield chunk; + } + } + responseStream = stream.Readable.from(enforceMaxContentLength(), { + objectMode: false + }); + } response.data = responseStream; settle(resolve, reject, response); } else { @@ -100898,6 +100996,27 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { req.on('socket', function handleRequestSocket(socket) { // default interval of sending ack packet is 1 minute socket.setKeepAlive(true, 1000 * 60); + + // Install a single 'error' listener per socket (not per request) to avoid + // accumulating listeners on pooled keep-alive sockets that get reassigned + // to new requests before the previous request's 'close' fires (issue #10780). + // The listener is bound to the socket's currently-active request via a + // symbol, which is swapped as the socket is reassigned. + if (!socket[kAxiosSocketListener]) { + socket.on('error', function handleSocketError(err) { + const current = socket[kAxiosCurrentReq]; + if (current && !current.destroyed) { + current.destroy(err); + } + }); + socket[kAxiosSocketListener] = true; + } + socket[kAxiosCurrentReq] = req; + req.once('close', function clearCurrentReq() { + if (socket[kAxiosCurrentReq] === req) { + socket[kAxiosCurrentReq] = null; + } + }); }); // Handle request timeout @@ -100944,7 +101063,28 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { abort(new CanceledError('Request stream has been aborted', config, req)); } }); - data.pipe(req); + + // Enforce maxBodyLength for streamed uploads on the native http/https + // transport (maxRedirects === 0); follow-redirects enforces it on the + // other path. See GHSA-5c9x-8gcm-mpgx. + let uploadStream = data; + if (config.maxBodyLength > -1 && config.maxRedirects === 0) { + const limit = config.maxBodyLength; + let bytesSent = 0; + uploadStream = stream.pipeline([data, new stream.Transform({ + transform(chunk, _enc, cb) { + bytesSent += chunk.length; + if (bytesSent > limit) { + return cb(new AxiosError('Request body larger than maxBodyLength limit', AxiosError.ERR_BAD_REQUEST, config, req)); + } + cb(null, chunk); + } + })], utils$1.noop); + uploadStream.on('error', err => { + if (!req.destroyed) req.destroy(err); + }); + } + uploadStream.pipe(req); } else { data && req.write(data); req.end(); @@ -101014,7 +101154,18 @@ const headersToObject = thing => thing instanceof AxiosHeaders ? { function mergeConfig(config1, config2) { // eslint-disable-next-line no-param-reassign config2 = config2 || {}; - const config = {}; + + // Use a null-prototype object so that downstream reads such as `config.auth` + // or `config.baseURL` cannot inherit polluted values from Object.prototype + // (see GHSA-q8qp-cvcw-x6jj). `hasOwnProperty` is restored as a non-enumerable + // own slot to preserve ergonomics for user code that relies on it. + const config = Object.create(null); + Object.defineProperty(config, 'hasOwnProperty', { + value: Object.prototype.hasOwnProperty, + enumerable: false, + writable: true, + configurable: true + }); function getMergedValue(target, source, prop, caseless) { if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) { return utils$1.merge.call({ @@ -101053,9 +101204,9 @@ function mergeConfig(config1, config2) { // eslint-disable-next-line consistent-return function mergeDirectKeys(a, b, prop) { - if (prop in config2) { + if (utils$1.hasOwnProp(config2, prop)) { return getMergedValue(a, b); - } else if (prop in config1) { + } else if (utils$1.hasOwnProp(config1, prop)) { return getMergedValue(undefined, a); } } @@ -101086,6 +101237,7 @@ function mergeConfig(config1, config2) { httpsAgent: defaultToConfig2, cancelToken: defaultToConfig2, socketPath: defaultToConfig2, + allowedSocketPaths: defaultToConfig2, responseEncoding: defaultToConfig2, validateStatus: mergeDirectKeys, headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) @@ -101096,7 +101248,9 @@ function mergeConfig(config1, config2) { }), function computeConfigValue(prop) { if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return; const merge = utils$1.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : mergeDeepProperties; - const configValue = merge(config1[prop], config2[prop], prop); + const a = utils$1.hasOwnProp(config1, prop) ? config1[prop] : undefined; + const b = utils$1.hasOwnProp(config2, prop) ? config2[prop] : undefined; + const configValue = merge(a, b, prop); utils$1.isUndefined(configValue) && merge !== mergeDirectKeys || (config[prop] = configValue); }); return config; @@ -101104,16 +101258,21 @@ function mergeConfig(config1, config2) { var resolveConfig = config => { const newConfig = mergeConfig({}, config); - let { - data, - withXSRFToken, - xsrfHeaderName, - xsrfCookieName, - headers, - auth - } = newConfig; + + // Read only own properties to prevent prototype pollution gadgets + // (e.g. Object.prototype.baseURL = 'https://evil.com'). See GHSA-q8qp-cvcw-x6jj. + const own = key => utils$1.hasOwnProp(newConfig, key) ? newConfig[key] : undefined; + const data = own('data'); + let withXSRFToken = own('withXSRFToken'); + const xsrfHeaderName = own('xsrfHeaderName'); + const xsrfCookieName = own('xsrfCookieName'); + let headers = own('headers'); + const auth = own('auth'); + const baseURL = own('baseURL'); + const allowAbsoluteUrls = own('allowAbsoluteUrls'); + const url = own('url'); newConfig.headers = headers = AxiosHeaders.from(headers); - newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config.params, config.paramsSerializer); + newConfig.url = buildURL(buildFullPath(baseURL, url, allowAbsoluteUrls), config.params, config.paramsSerializer); // HTTP basic authentication if (auth) { @@ -101140,9 +101299,15 @@ var resolveConfig = config => { // Specifically not if we're in a web worker, or react-native. if (platform.hasStandardBrowserEnv) { - withXSRFToken && utils$1.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig)); - if (withXSRFToken || withXSRFToken !== false && isURLSameOrigin(newConfig.url)) { - // Add xsrf header + if (utils$1.isFunction(withXSRFToken)) { + withXSRFToken = withXSRFToken(newConfig); + } + + // Strict boolean check — prevents proto-pollution gadgets (e.g. Object.prototype.withXSRFToken = 1) + // and misconfigurations (e.g. "false") from short-circuiting the same-origin check and leaking + // the XSRF token cross-origin. See GHSA-xx6v-rp6x-q39c. + const shouldSendXSRF = withXSRFToken === true || withXSRFToken == null && isURLSameOrigin(newConfig.url); + if (shouldSendXSRF) { const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName); if (xsrfValue) { headers.set(xsrfHeaderName, xsrfValue); @@ -101484,16 +101649,18 @@ const factory = env => { const encodeText = isFetchSupported && (typeof TextEncoder$1 === 'function' ? (encoder => str => encoder.encode(str))(new TextEncoder$1()) : async str => new Uint8Array(await new Request(str).arrayBuffer())); const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => { let duplexAccessed = false; - const body = new ReadableStream$1(); - const hasContentType = new Request(platform.origin, { - body, + const request = new Request(platform.origin, { + body: new ReadableStream$1(), method: 'POST', get duplex() { duplexAccessed = true; return 'half'; } - }).headers.has('Content-Type'); - body.cancel(); + }); + const hasContentType = request.headers.has('Content-Type'); + if (request.body != null) { + request.body.cancel(); + } return duplexAccessed && !hasContentType; }); const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response('').body)); @@ -101585,6 +101752,15 @@ const factory = env => { // Cloudflare Workers throws when credentials are defined // see https://github.com/cloudflare/workerd/issues/902 const isCredentialsSupported = isRequestSupported && 'credentials' in Request.prototype; + + // If data is FormData and Content-Type is multipart/form-data without boundary, + // delete it so fetch can set it correctly with the boundary + if (utils$1.isFormData(data)) { + const contentType = headers.getContentType(); + if (contentType && /^multipart\/form-data/i.test(contentType) && !/boundary=/i.test(contentType)) { + headers.delete('content-type'); + } + } const resolvedOptions = { ...fetchOptions, signal: composedSignal, @@ -101880,7 +102056,9 @@ function assertOptions(options, schema, allowUnknown) { let i = keys.length; while (i-- > 0) { const opt = keys[i]; - const validator = schema[opt]; + // Use hasOwnProperty so a polluted Object.prototype. cannot supply + // a non-function validator and cause a TypeError. See GHSA-q8qp-cvcw-x6jj. + const validator = Object.prototype.hasOwnProperty.call(schema, opt) ? schema[opt] : undefined; if (validator) { const value = options[opt]; const result = value === undefined || validator(value, opt, options); diff --git a/package-lock.json b/package-lock.json index 16a91ef..9fa616b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@actions/http-client": "^3.0.2", "@actions/tool-cache": "^3.0.1", "@octokit/auth-unauthenticated": "^5.0.1", - "axios": "1.15.0", + "axios": "1.15.2", "cachedir": "^2.4.0", "semver": "^7.6.0" }, @@ -809,9 +809,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", diff --git a/package.json b/package.json index 9c4e292..f00c5bf 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@actions/http-client": "^3.0.2", "@actions/tool-cache": "^3.0.1", "@octokit/auth-unauthenticated": "^5.0.1", - "axios": "1.15.0", + "axios": "1.15.2", "cachedir": "^2.4.0", "semver": "^7.6.0" },