禁用时,将不使用梯子
+ PAC没有拦截到的域名,可以在此处定义;配置为启用时,可为该域名选择代理服务器,默认优先使用 ID 1,若 ID 1 不存在则自动使用 ID 0;配置为禁用时,将不使用梯子
ID 0 为内置默认服务器;在此处新增的服务器 ID 会依次为 1、2、3...。production.cloudflare.docker.com 选择 TLS 1.2。每行右侧开关可单独启用/停用;远程下发的规则会显示为停用状态,由你自行启用。未匹配到或停用的域名遵循“允许TLS1.2”开关。
+ core.log、gui.log、server.log 也不会再写入。
+ DevSidecar Error:
-目标网站请求错误:【${e.code || (e.status || 'UNKNOWN')}】 ${e.message}
-目标地址:${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}
`, - ) + const errorMsg = `目标网站请求错误:【${e.code || (e.status || 'UNKNOWN')}】 ${e.message}` + const retryInfo = context.retryCount > 0 ? `自动重试:已尝试 ${context.retryCount} 次` : '' + const target = `目标地址:${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}` + + if (acceptsHtml) { + res.write(` +DevSidecar Error:
+${errorMsg}
+ ${retryInfo ? `${retryInfo}
` : ''} +${target}
`, + ) + } else { + res.write(`DevSidecar Error:\n${errorMsg}\n${retryInfo ? `${retryInfo}\n` : ''}${target}`) + } } catch { // do nothing } diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js index 7a2f30b97d..b621da5f69 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js @@ -2,6 +2,9 @@ const defaultDns = require('node:dns') const net = require('node:net') const log = require('../../../utils/util.log.server') const speedTest = require('../../speed') +const cloudflareRoute = require('../../cloudflareRoute') +const trafficMonitor = require('../../traffic/TrafficMonitor') +const { isZeroIp } = require('../../dns/util.ip') // HTTP/2 头值只允许 ASCII 可见字符,需过滤中文等非 ASCII 字符 function safeHeaderValue (value) { @@ -16,6 +19,36 @@ function getAddressFamily (ip) { return net.isIP(ip) === 6 ? 6 : 4 } +// DNS 返回的全部地址都是零 IP(0.0.0.0 / ::)时,判定为 Adblock 拦截,通知流量统计忽略该域名 +function markBlockedIfAllZero (hostname, address) { + const list = Array.isArray(address) ? address : [address] + if (list.length === 0) { + return + } + const allZero = list.every((item) => { + const ip = item && item.address ? item.address : item + return isZeroIp(ip) + }) + if (allZero) { + trafficMonitor.markBlocked(hostname) + } +} + +function rewriteCloudflareIp (hostname, ip, isPreSet) { + if (!isValidIpAddress(ip)) { + return ip + } + // 预设 IP 优先级最高,不参与 Cloudflare 路由重定向 + if (isPreSet) { + return ip + } + const rewritten = cloudflareRoute.rewriteIp(ip, hostname) + if (rewritten !== ip) { + log.info(`[cloudflare-route] 命中 Cloudflare IP 段,重写: ${hostname}: ${ip} -> ${rewritten}`) + } + return rewritten +} + function respondLookup (callback, ip, family, all) { if (all) { callback(null, [{ address: ip, family }]) @@ -34,12 +67,13 @@ function createIpChecker (tester) { for (let i = 0; i < tester.backupList.length; i++) { const item = tester.backupList[i] if (item.host === ip) { - if (item.time > 0) { - return true // IP测速成功 - } + // 先判断失败标记:失败 IP 在重新测速成功前不再被回退 DNS 选用 if (item.status === 'failed') { return false // IP测速失败 } + if (item.time > 0) { + return true // IP测速成功 + } break } } @@ -57,15 +91,44 @@ module.exports = { return (hostname, options, callback) => { const all = options && options.all === true + const defaultLookup = (options, callback) => { + defaultDns.lookup(hostname, options, (err, address, family) => { + if (!err) { + markBlockedIfAllZero(hostname, address) + if (Array.isArray(address)) { + address = address.map((item) => { + const newIp = rewriteCloudflareIp(hostname, item.address) + return { address: newIp, family: getAddressFamily(newIp) } + }) + } else { + address = rewriteCloudflareIp(hostname, address) + family = getAddressFamily(address) + } + if (res) { + const first = Array.isArray(address) ? address[0] : { address, family } + if (first && isValidIpAddress(first.address)) { + res.setHeader('DS-DNS', `default: ${first.address} (IPv${first.family})`) + } + } + } + callback(err, address, family) + }) + } const tester = speedTest.getSpeedTester(hostname, port) if (tester) { const aliveIpObj = tester.pickFastAliveIpObj() - if (aliveIpObj && isValidIpAddress(aliveIpObj.host)) { + if (aliveIpObj && isValidIpAddress(aliveIpObj.host) && !isZeroIp(aliveIpObj.host)) { const addressFamily = getAddressFamily(aliveIpObj.host) log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`) + if (isDnsIntercept) { + isDnsIntercept.dns = dns + isDnsIntercept.hostname = hostname + isDnsIntercept.ip = aliveIpObj.host + isDnsIntercept.tester = tester + } if (res) { const dnsLabel = aliveIpObj.dns === '预设IP' ? 'PreSet' : safeHeaderValue(aliveIpObj.dns) - res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${dnsLabel}`) + res.setHeader('DS-DNS', `${dnsLabel}: ${aliveIpObj.host} (IPv${addressFamily})`) } respondLookup(callback, aliveIpObj.host, addressFamily, all) return @@ -79,10 +142,19 @@ module.exports = { // 无已测速的存活 IP,轮转分配未失败 IP 逐个探测,并发请求自动分散 if (tester && tester.backupList.length > 0) { const probe = tester.pickNextForProbing() - if (probe && isValidIpAddress(probe.host)) { + if (probe && isValidIpAddress(probe.host) && !isZeroIp(probe.host)) { const addressFamily = getAddressFamily(probe.host) log.info(`----- ${action}: ${hostname}, use probing ip: ${probe.host} (family: ${addressFamily})${target} -----`) - if (isDnsIntercept) { isDnsIntercept.tester = tester } + if (isDnsIntercept) { + isDnsIntercept.dns = dns + isDnsIntercept.hostname = hostname + isDnsIntercept.ip = probe.host + isDnsIntercept.tester = tester + } + if (res) { + const dnsLabel = probe.dns === '预设IP' ? 'PreSet' : safeHeaderValue(probe.dns) + res.setHeader('DS-DNS', `${dnsLabel}: ${probe.host} (IPv${addressFamily})`) + } respondLookup(callback, probe.host, addressFamily, all) return } @@ -90,17 +162,21 @@ module.exports = { dns.lookup(hostname, { ipChecker, family }).then((ip) => { if (ip !== hostname && isValidIpAddress(ip)) { + markBlockedIfAllZero(hostname, ip) + ip = rewriteCloudflareIp(hostname, ip, dns && dns.dnsName === 'PreSet') const addressFamily = getAddressFamily(ip) if (isDnsIntercept) { isDnsIntercept.dns = dns isDnsIntercept.hostname = hostname isDnsIntercept.ip = ip - if (tester) isDnsIntercept.tester = tester + if (tester) { + isDnsIntercept.tester = tester + } } log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}(family: ${addressFamily})${target} -----`) if (res) { const dnsLabel = dns.dnsName === '预设IP' ? 'PreSet' : safeHeaderValue(dns.dnsName) - res.setHeader('DS-DNS-Lookup', `DNS: ${ip} (IPv${addressFamily}) ${dnsLabel}`) + res.setHeader('DS-DNS', `${dnsLabel}: ${ip} (IPv${addressFamily})`) } respondLookup(callback, ip, addressFamily, all) } else { @@ -108,12 +184,62 @@ module.exports = { if (ip != null && ip !== hostname && !isValidIpAddress(ip)) { log.warn(`----- ${action}: ${hostname}, dns returned invalid ip '${ip}'${target}, fallback to default DNS`) } - log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) - defaultDns.lookup(hostname, options, callback) + // 不继承原 family 选项:强制 IPv6 时若系统 DNS 无 AAAA,会误报 ENOTFOUND,此时应允许系统 DNS 返回 IPv4 + const defaultOptions = { ...options } + delete defaultOptions.family + log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, defaultOptions, ', dns:', dns) + defaultLookup(defaultOptions, callback) } }).catch((err) => { log.error(`----- ${action}: ${hostname}, dns lookup error${target}, options:`, options, `, error:`, err) - defaultDns.lookup(hostname, options, callback) + const defaultOptions = { ...options } + delete defaultOptions.family + defaultLookup(defaultOptions, callback) + }) + } + }, + + // 未配置自定义 DNS 时,使用系统默认 DNS 解析,并捕获 IP 写入响应头 + createDefaultLookupFunc (res, action, target) { + target = target ? `, target: ${target}` : '' + + return (hostname, options, callback) => { + defaultDns.lookup(hostname, options, (err, address, family) => { + if (err) { + log.error(`----- ${action}: ${hostname}, default dns lookup error${target}, error:`, err) + callback(err, address, family) + return + } + markBlockedIfAllZero(hostname, address) + if (Array.isArray(address)) { + address = address.map((item) => { + const newIp = rewriteCloudflareIp(hostname, item.address) + return { address: newIp, family: getAddressFamily(newIp) } + }) + } else { + address = rewriteCloudflareIp(hostname, address) + family = getAddressFamily(address) + } + const first = Array.isArray(address) ? address[0] : { address, family } + const ip = first && first.address + const fam = first && first.family + if (ip && isValidIpAddress(ip)) { + log.info(`----- ${action}: ${hostname}, use default DNS: ${ip}(family: ${fam})${target} -----`) + if (res) { + res.setHeader('DS-DNS', `default: ${ip} (IPv${fam})`) + } + } + // Node 20+ 默认开启 autoSelectFamily,会以 { all: true } 调用自定义 lookup, + // 此时必须回传完整的地址数组;只回传单个 IP 会被 Node 当作数组解析, + // 导致 ERR_INVALID_IP_ADDRESS: Invalid IP address: undefined + if (Array.isArray(address) && address.length === 0) { + // 防御:all 模式下解析结果为空时,按 ENOTFOUND 处理,避免回传空数组 + const notFound = new Error(`getaddrinfo ENOTFOUND ${hostname}`) + notFound.code = 'ENOTFOUND' + callback(notFound) + return + } + callback(null, Array.isArray(address) ? address : ip, family) }) } }, diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/index.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/index.js index fa292a99a2..2bee5e84ee 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/index.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/index.js @@ -1,6 +1,8 @@ const http = require('node:http') const log = require('../../../utils/util.log.server') const speedTest = require('../../speed/index.js') +const trafficMonitor = require('../../traffic/TrafficMonitor') +const { startProcessResolver } = require('../../traffic/processResolver') const config = require('../common/config') const tlsUtils = require('../tls/tlsUtils') const createConnectHandler = require('./createConnectHandler') @@ -41,19 +43,13 @@ module.exports = { port = ~~port const speedTestConfig = dnsConfig.speedTest - const dnsMap = dnsConfig.dnsMap if (speedTestConfig) { - const dnsProviders = speedTestConfig.dnsProviders - const map = {} - for (const dnsProvider of dnsProviders) { - if (dnsMap[dnsProvider]) { - map[dnsProvider] = dnsMap[dnsProvider] - } - } - speedTest.initSpeedTest({ ...speedTestConfig, dnsMap: map }) + // 将完整 dnsConfig 传给测速器,由测速器按域名动态选择 DNS: + // 预设IP > DNS设置(mapping) > IP测速勾选的 dnsProviders + speedTest.initSpeedTest({ ...speedTestConfig, dnsConfig }) } - const requestHandler = createRequestHandler( + const rawRequestHandler = createRequestHandler( createIntercepts, middlewares, externalProxy, @@ -62,6 +58,12 @@ module.exports = { compatibleConfig, ) + // 所有 HTTP 请求(含 MITM 解密后的 HTTPS 请求)都会经过这里,统一做流量统计 + const requestHandler = (req, res, ssl) => { + trafficMonitor.attachRequest(req, res) + rawRequestHandler(req, res, ssl) + } + const upgradeHandler = createUpgradeHandler(setting) const fakeServersCenter = createFakeServerCenter({ @@ -153,6 +155,10 @@ module.exports = { serverListen(httpsServer, true, httpsPort, host) serverListen(httpServer, false, httpPort, host) + // 启动流量统计与进程解析 + trafficMonitor.start([httpPort, httpsPort]) + startProcessResolver(trafficMonitor) + return [httpsServer, httpServer] }, createCA (caPaths) { diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index fa9ac900f1..b24bb4d313 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -4,6 +4,8 @@ const _ = require('lodash') const log = require('../../utils/util.log.server') const config = require('./config.js') const matchUtil = require('../../utils/util.match.js') +const cloudflareRoute = require('../cloudflareRoute') +const { isZeroIp } = require('../dns/util.ip') const { configFromFiles } = require('@docmirror/dev-sidecar/src/config/index.js') const familyMapping = matchUtil.domainMapRegexply(configFromFiles.server.dns.familyMapping) @@ -14,7 +16,7 @@ const DISABLE_TIMEOUT = 60 * 60 * 1000 class SpeedTester { constructor ({ hostname, port }) { - this.dnsMap = config.getConfig().dnsMap + this.dnsConfig = config.getConfig().dnsConfig || null this.hostname = hostname this.port = port || 443 @@ -30,6 +32,8 @@ class SpeedTester { this.tryTestCount = 0 this.isTesting = false this.isTestingBackups = false + this.pendingTest = false + this.waitingForCloudflareReady = false this._probeIndex = 0 // 按需探测的轮转索引 @@ -61,20 +65,31 @@ class SpeedTester { // 按需探测结果反馈:记录 IP 成败,供后续请求决策 reportProbeResult (host, success) { const item = this.backupList.find(i => i.host === host) - if (!item) return + if (!item) { + return + } item._probing = false if (success) { item.status = 'success' item.time = item.time || 1 - this.alive.push(item) + if (!this.alive.some(i => i.host === host)) { + this.alive.push(item) + } } else { item.status = 'failed' + // 从存活列表中移除失败 IP,避免后续请求和自动重试继续使用这个已失败的 IP + this.alive = this.alive.filter(i => i.host !== host) } } pickFastAliveIpObj () { this.touch() + // 防御:过滤掉状态已被标记为 failed 的存活 IP + if (this.alive.length > 0) { + this.alive = this.alive.filter(item => item.status !== 'failed') + } + if (this.alive.length === 0) { if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) { this.testBackups() // 异步 @@ -114,14 +129,58 @@ class SpeedTester { }, config.getConfig().interval) } - async getIpListFromDns (dnsMap) { + // 按域名动态选择测速使用的 DNS: + // 1. 预设IP优先级最高; + // 2. 已在 DNS设置 中配置了 DNS 和 IP版本 的域名,使用该设置项; + // 3. 其余域名使用 IP测速 中勾选的 dnsProviders。 + getEffectiveDnsConfig () { + const dnsConfig = this.dnsConfig + if (dnsConfig == null || dnsConfig.dnsMap == null) { + return { dnsMap: {}, family: null } + } + + // 1. 预设IP优先级最高 + const preSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, this.hostname, 'matched preSetIpList(speedTest)') + if (preSetIpList) { + return { + dnsMap: { PreSet: dnsConfig.dnsMap.PreSet }, + family: null, + } + } + + // 2. DNS设置 中已配置的域名,使用其配置的 DNS 和 IP版本 + const dnsData = matchUtil.matchHostname(dnsConfig.mapping, this.hostname, 'speedTest get dns data') + if (dnsData && dnsConfig.dnsMap[dnsData.dnsName]) { + return { + dnsMap: { [dnsData.dnsName]: dnsConfig.dnsMap[dnsData.dnsName] }, + family: dnsData.family, + } + } + + // 3. 其余域名使用 IP测速 中勾选的 dnsProviders + const dnsMap = {} + const dnsProviders = config.getConfig().dnsProviders || [] + for (const dnsProvider of dnsProviders) { + if (dnsConfig.dnsMap[dnsProvider]) { + dnsMap[dnsProvider] = dnsConfig.dnsMap[dnsProvider] + } + } + return { dnsMap, family: null } + } + + async getIpListFromDns (dnsMap, family) { const ips = {} const promiseList = [] for (const dnsKey in dnsMap) { const dns = dnsMap[dnsKey] - const one = this.getFromOneDns(dns).then((ipList) => { + const one = this.getFromOneDns(dns, family).then((ipList) => { if (ipList && ipList.length > 0) { for (const ip of ipList) { + // Adblock 拦截返回的 0.0.0.0 / :: 不参与测速,避免出现在 IP 测速列表和 DNS 优选统计中 + if (isZeroIp(ip)) { + log.info(`[speed] 忽略零 IP 地址(Adblock 拦截特征): ${this.hostname} -> ${ip}`) + continue + } ips[ip] = { dns: ipList.isPreSet === true ? '预设IP' : dnsKey } } } @@ -132,18 +191,36 @@ class SpeedTester { const items = [] for (const ip in ips) { - items.push({ host: ip, dns: ips[ip].dns }) + const item = { host: ip, dns: ips[ip].dns } + // 预设 IP 优先级最高,不参与 Cloudflare 路由重定向 + if (item.dns !== '预设IP') { + const rewritten = cloudflareRoute.rewriteIp(ip, this.hostname) + if (rewritten !== ip) { + // 命中 Cloudflare 路由重定向:测速内容替换为优选 IP 或 CNAME 域名 + item.host = cloudflareRoute.getPreferredEndpoint() || rewritten + item.cf = true + item.cfOriginalHost = ip + } + } + items.push(item) } return items } - async getFromOneDns (dns) { - const family = Number.parseInt(matchUtil.matchHostname(familyMapping, this.hostname, 'get family')) === 6 ? 6 : 4 - return await dns._lookupWithPreSetIpList(this.hostname, { family }) + async getFromOneDns (dns, family) { + const lookupFamily = family != null + ? (Number.parseInt(family) === 6 ? 6 : 4) + : (Number.parseInt(matchUtil.matchHostname(familyMapping, this.hostname, 'get family')) === 6 ? 6 : 4) + return await dns._lookupWithPreSetIpList(this.hostname, { family: lookupFamily }) } async test () { if (this.isTesting) { + // 正在等待 Cloudflare 路由数据就绪的测速,就绪后自然会用最新数据继续,无需再挂起一轮 + if (!this.waitingForCloudflareReady) { + // 当前正在测速时先挂起,待本轮结束后再重测,避免错过关键时机 + this.pendingTest = true + } log.debug(`[speed] test skipped (already running): ${this.hostname}`) return } @@ -152,9 +229,32 @@ class SpeedTester { log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`) try { - const newList = await this.getIpListFromDns(this.dnsMap) - const newBackupList = [...newList, ...this.backupList] - this.backupList = _.unionBy(newBackupList, 'host') + // Cloudflare 路由重定向开启后,若 IP 段/优选地址尚未就绪,先等待就绪再解析测速, + // 避免首轮测速拿到原始 Cloudflare IP + if (cloudflareRoute.isEnabled() && !cloudflareRoute.isReady()) { + this.waitingForCloudflareReady = true + log.info(`[speed] 等待 Cloudflare 路由数据就绪后开始测速: ${this.hostname}`) + try { + await cloudflareRoute.whenReady() + log.info(`[speed] Cloudflare 路由数据已就绪,开始测速: ${this.hostname}`) + } catch (e) { + log.warn(`[speed] 等待 Cloudflare 路由数据就绪超时,将按原始解析测速: ${this.hostname}, error: ${e.message}`) + } finally { + this.waitingForCloudflareReady = false + } + } + + const { dnsMap, family } = this.getEffectiveDnsConfig() + const newList = await this.getIpListFromDns(dnsMap, family) + // Cloudflare 路由重定向命中后,只测优选 IP/域名,不再保留旧的原始解析 IP + if (newList.some(item => item.cf === true)) { + this.backupList = _.unionBy(newList, 'host') + } else { + const newBackupList = [...newList, ...this.backupList] + this.backupList = _.unionBy(newBackupList, 'host') + } + // 兜底:剔除历史遗留的零 IP(Adblock 拦截特征),确保不会进入测速与优选 + this.backupList = this.backupList.filter(item => !isZeroIp(item.host)) await this.testBackups() log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`) if (config.notify) { @@ -164,6 +264,10 @@ class SpeedTester { log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e) } finally { this.isTesting = false + if (this.pendingTest) { + this.pendingTest = false + this.test() + } } } diff --git a/packages/mitmproxy/src/lib/speed/config.js b/packages/mitmproxy/src/lib/speed/config.js index dd7a0a229d..604c1e67ba 100644 --- a/packages/mitmproxy/src/lib/speed/config.js +++ b/packages/mitmproxy/src/lib/speed/config.js @@ -1,5 +1,7 @@ const config = { dnsMap: {}, + dnsConfig: null, + dnsProviders: [], } module.exports = { getConfig () { diff --git a/packages/mitmproxy/src/lib/speed/index.js b/packages/mitmproxy/src/lib/speed/index.js index e67071f2cc..0c4647e635 100644 --- a/packages/mitmproxy/src/lib/speed/index.js +++ b/packages/mitmproxy/src/lib/speed/index.js @@ -1,6 +1,7 @@ const _ = require('lodash') const log = require('../../utils/util.log.server') const config = require('./config') +const cloudflareRoute = require('../cloudflareRoute') const SpeedTester = require('./SpeedTester.js') const SpeedTestPool = { @@ -29,15 +30,22 @@ function addSpeedTest (hostname, port) { } function initSpeedTest (runtimeConfig) { - const { enabled, hostnameList } = runtimeConfig + const { enabled, hostnameList, dnsConfig } = runtimeConfig const conf = config.getConfig() _.merge(conf, runtimeConfig) + // 保存完整的 dnsConfig,供 SpeedTester 按域名动态选择 DNS + conf.dnsConfig = dnsConfig if (!enabled) { return } _.forEach(hostnameList, (hostname) => { addSpeedTest(hostname) }) + // Cloudflare 路由数据就绪后重新测速一次,避免首次测速时 CF 段尚未拉取导致未重写 + cloudflareRoute.setReadyListener(() => { + log.info('[speed] Cloudflare 路由数据已就绪,重新测速一次') + reSpeedTest() + }) log.info('[speed] enabled,SpeedTestPool:', SpeedTestPool) } diff --git a/packages/mitmproxy/src/lib/traffic/TrafficMonitor.js b/packages/mitmproxy/src/lib/traffic/TrafficMonitor.js new file mode 100644 index 0000000000..b0335e0620 --- /dev/null +++ b/packages/mitmproxy/src/lib/traffic/TrafficMonitor.js @@ -0,0 +1,332 @@ +const log = require('../../utils/util.log.server') + +const DEFAULT_PROXY_PORTS = [31180, 31181] +const ENTRY_TTL = 60 * 1000 +const BLOCKED_HOST_TTL = 5 * 60 * 1000 + +function normalizeHost (host) { + if (!host) { + return 'unknown' + } + let value = String(host).trim() + if (value.startsWith('[')) { + value = value.slice(1, value.indexOf(']') >= 0 ? value.indexOf(']') : value.length) + } else if (value.includes(':')) { + value = value.slice(0, value.indexOf(':')) + } + return value || 'unknown' +} + +class TrafficMonitor { + constructor () { + this.entries = new Map() // clientPort -> entry + this.domainMap = new Map() // host -> { requests, errors } + this.blockedHosts = new Map() // host -> 过期时间戳(Adblock 拦截域名) + this.proxyPorts = DEFAULT_PROXY_PORTS + this.timer = null + this.lastSampleTime = Date.now() + } + + start (proxyPorts) { + if (this.timer) { + return + } + if (proxyPorts && proxyPorts.length > 0) { + this.proxyPorts = proxyPorts + } + this.lastSampleTime = Date.now() + this.timer = setInterval(() => this.sampleAndSend(), 1000) + if (this.timer.unref) { + this.timer.unref() + } + log.info(`[traffic] 流量统计已启动,代理端口: ${this.proxyPorts.join(', ')}`) + } + + stop () { + if (this.timer) { + clearInterval(this.timer) + this.timer = null + } + } + + setProcessName (clientPort, name) { + const entry = this.entries.get(clientPort) + if (entry && name) { + entry.processName = name + entry.lastActive = Date.now() + } + } + + getActiveClientPorts () { + return [...this.entries.keys()] + } + + attachRequest (req, res) { + const socket = req.socket || (req.stream && req.stream.session && req.stream.session.socket) + if (!socket) { + return + } + const clientPort = socket.remotePort + if (!clientPort) { + return + } + const host = normalizeHost(req.headers.host || req.authority || req.url) + this.ensureEntry(socket, clientPort, host) + this.markRequest(clientPort, host) + + res.once('finish', () => { + if (res.statusCode >= 400) { + this.markError(clientPort, host) + } + }) + res.once('close', () => { + if (!res.writableFinished) { + this.markError(clientPort, host) + } + }) + } + + attachConnect (req, cltSocket) { + const clientPort = cltSocket.remotePort + if (!clientPort) { + return + } + const host = normalizeHost(req.url) + this.ensureEntry(cltSocket, clientPort, host) + this.markRequest(clientPort, host) + cltSocket.once('error', () => { + this.markError(clientPort, host) + }) + } + + markConnectError (host) { + const normalized = normalizeHost(host) + this.markError(null, normalized) + } + + ensureEntry (socket, clientPort, host) { + const now = Date.now() + let entry = this.entries.get(clientPort) + if (!entry) { + entry = { + clientPort, + host, + processName: '未知进程', + socket, + bytesUp: 0, + bytesDown: 0, + lastBytesUp: socket.bytesRead || 0, + lastBytesDown: socket.bytesWritten || 0, + lastSampleBytesUp: 0, + lastSampleBytesDown: 0, + speedUp: 0, + speedDown: 0, + requests: 0, + errors: 0, + lastActive: now, + } + this.entries.set(clientPort, entry) + } else { + entry.host = host + entry.socket = socket + entry.lastBytesUp = socket.bytesRead || 0 + entry.lastBytesDown = socket.bytesWritten || 0 + entry.lastActive = now + } + socket.once('close', () => { + this.finalizeSocket(clientPort) + }) + return entry + } + + finalizeSocket (clientPort) { + const entry = this.entries.get(clientPort) + if (!entry) { + return + } + const socket = entry.socket + if (socket) { + entry.bytesUp += Math.max(0, (socket.bytesRead || 0) - entry.lastBytesUp) + entry.bytesDown += Math.max(0, (socket.bytesWritten || 0) - entry.lastBytesDown) + entry.lastBytesUp = socket.bytesRead || 0 + entry.lastBytesDown = socket.bytesWritten || 0 + entry.socket = null + } + entry.lastActive = Date.now() + } + + markRequest (clientPort, host) { + const normalized = normalizeHost(host) + if (this.isBlockedHost(normalized)) { + return + } + const now = Date.now() + if (clientPort != null) { + const entry = this.entries.get(clientPort) + if (entry) { + entry.requests++ + entry.lastActive = now + } + } + let domain = this.domainMap.get(normalized) + if (!domain) { + domain = { requests: 0, errors: 0 } + this.domainMap.set(normalized, domain) + } + domain.requests++ + } + + markError (clientPort, host) { + const normalized = normalizeHost(host) + if (this.isBlockedHost(normalized)) { + return + } + const now = Date.now() + if (clientPort != null) { + const entry = this.entries.get(clientPort) + if (entry) { + entry.errors++ + entry.lastActive = now + } + } + let domain = this.domainMap.get(normalized) + if (!domain) { + domain = { requests: 0, errors: 0 } + this.domainMap.set(normalized, domain) + } + domain.errors++ + } + + // Adblock 规则命中时 DNS 返回 0.0.0.0 / ::,这类请求/错误不应进入域名错误统计 + markBlocked (host) { + const normalized = normalizeHost(host) + if (normalized === 'unknown') { + return + } + const wasBlocked = this.isBlockedHost(normalized) + this.blockedHosts.set(normalized, Date.now() + BLOCKED_HOST_TTL) + this.domainMap.delete(normalized) + if (!wasBlocked) { + log.info(`[traffic] 已忽略 Adblock 拦截域名: ${normalized}`) + } + } + + isBlockedHost (normalized) { + const expiresAt = this.blockedHosts.get(normalized) + if (!expiresAt) { + return false + } + if (Date.now() < expiresAt) { + return true + } + this.blockedHosts.delete(normalized) + return false + } + + sampleAndSend () { + const now = Date.now() + const dt = Math.max(1, (now - this.lastSampleTime) / 1000) + this.lastSampleTime = now + + for (const entry of this.entries.values()) { + const socket = entry.socket + if (socket && !socket.destroyed) { + const bytesRead = socket.bytesRead || 0 + const bytesWritten = socket.bytesWritten || 0 + entry.bytesUp += Math.max(0, bytesRead - entry.lastBytesUp) + entry.bytesDown += Math.max(0, bytesWritten - entry.lastBytesDown) + entry.lastBytesUp = bytesRead + entry.lastBytesDown = bytesWritten + } + + entry.speedUp = Math.round(Math.max(0, entry.bytesUp - entry.lastSampleBytesUp) / dt) + entry.speedDown = Math.round(Math.max(0, entry.bytesDown - entry.lastSampleBytesDown) / dt) + entry.lastSampleBytesUp = entry.bytesUp + entry.lastSampleBytesDown = entry.bytesDown + } + + // 清理已关闭且长时间无活动的连接条目 + for (const [clientPort, entry] of this.entries) { + if (!entry.socket && now - entry.lastActive > ENTRY_TTL) { + this.entries.delete(clientPort) + } + } + + const processMap = new Map() + let totalRequests = 0 + let totalErrors = 0 + let totalBytesUp = 0 + let totalBytesDown = 0 + for (const entry of this.entries.values()) { + const name = entry.processName || '未知进程' + let process = processMap.get(name) + if (!process) { + process = { + processName: name, + bytesUp: 0, + bytesDown: 0, + speedUp: 0, + speedDown: 0, + requests: 0, + errors: 0, + connections: 0, + } + processMap.set(name, process) + } + process.bytesUp += entry.bytesUp + process.bytesDown += entry.bytesDown + process.speedUp += entry.speedUp + process.speedDown += entry.speedDown + process.requests += entry.requests + process.errors += entry.errors + // 连接数只统计当前仍存活的 socket,socket 关闭后实时下降 + if (entry.socket && !entry.socket.destroyed) { + process.connections += 1 + } + + totalBytesUp += entry.bytesUp + totalBytesDown += entry.bytesDown + } + + const domainStats = [] + for (const [host, domain] of this.domainMap) { + totalRequests += domain.requests + totalErrors += domain.errors + domainStats.push({ + host, + requests: domain.requests, + errors: domain.errors, + errorRate: domain.requests > 0 ? Number((domain.errors / domain.requests).toFixed(4)) : 0, + }) + } + domainStats.sort((a, b) => b.errors - a.errors || b.requests - a.requests) + + const stats = { + processStats: [...processMap.values()].sort((a, b) => b.speedDown - a.speedDown || b.speedUp - a.speedUp || b.bytesDown - a.bytesDown), + domainStats: domainStats.slice(0, 100), + total: { + requests: totalRequests, + errors: totalErrors, + errorRate: totalRequests > 0 ? Number((totalErrors / totalRequests).toFixed(4)) : 0, + bytesUp: totalBytesUp, + bytesDown: totalBytesDown, + }, + updateTime: now, + } + + this.send(stats) + } + + send (stats) { + if (typeof process.send !== 'function') { + return + } + try { + process.send({ type: 'traffic', event: { key: 'stats', value: stats } }) + } catch (e) { + log.debug('[traffic] 发送流量统计失败:', e.message) + } + } +} + +module.exports = new TrafficMonitor() diff --git a/packages/mitmproxy/src/lib/traffic/processResolver.js b/packages/mitmproxy/src/lib/traffic/processResolver.js new file mode 100644 index 0000000000..de832e1f16 --- /dev/null +++ b/packages/mitmproxy/src/lib/traffic/processResolver.js @@ -0,0 +1,120 @@ +const { execFile } = require('node:child_process') + +function execFileAsync (file, args) { + return new Promise((resolve, reject) => { + execFile(file, args, { windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (error, stdout) => { + if (error) { + reject(error) + } else { + resolve(stdout || '') + } + }) + }) +} + +// 解析 netstat -ano -p tcp: +// 找出所有 ESTABLISHED 连接的“本地端口 -> PID”映射。 +// 监听器只对 TrafficMonitor 当前记录的客户端端口查表,因此不会误用代理服务端端口。 +function parseNetstat (output) { + const portPidMap = new Map() + for (const line of output.split(/\r?\n/)) { + const match = line.match(/^\s*TCP\s+([^:]+):(\d+)\s+([^:]+):(\d+)\s+(\S+)\s+(\d+)/) + if (!match) { + continue + } + const localPort = Number.parseInt(match[2], 10) + const state = match[5] + const pid = Number.parseInt(match[6], 10) + if (state === 'ESTABLISHED') { + portPidMap.set(localPort, pid) + } + } + return portPidMap +} + +// 解析 tasklist /FO CSV /NH:"Image Name","PID","Session Name","Session#","Mem Usage" +function parseTasklist (output) { + const pidNameMap = new Map() + for (const line of output.split(/\r?\n/)) { + const parts = line.split('","') + if (parts.length < 2) { + continue + } + const name = parts[0].replace(/^"/, '') + const pid = Number.parseInt(parts[1], 10) + if (!Number.isNaN(pid)) { + pidNameMap.set(pid, name) + } + } + return pidNameMap +} + +function startProcessResolver (monitor, interval = 2500) { + if (!monitor) { + return () => {} + } + + let running = false + let timer = null + + const resolveOnce = async () => { + if (running) { + return + } + running = true + try { + const activePorts = monitor.getActiveClientPorts() + if (!activePorts || activePorts.length === 0) { + return + } + + const netstatOutput = await execFileAsync('netstat.exe', ['-ano', '-p', 'tcp']) + const portPidMap = parseNetstat(netstatOutput) + if (portPidMap.size === 0) { + return + } + + const pids = [...new Set(activePorts.map((port) => portPidMap.get(port)).filter((pid) => pid != null))] + if (pids.length === 0) { + return + } + + const tasklistOutput = await execFileAsync('tasklist.exe', ['/FO', 'CSV', '/NH']) + const pidNameMap = parseTasklist(tasklistOutput) + + for (const clientPort of activePorts) { + const pid = portPidMap.get(clientPort) + if (pid == null) { + continue + } + const name = pidNameMap.get(pid) + if (name) { + monitor.setProcessName(clientPort, name) + } + } + } catch { + // 进程解析失败不影响流量统计 + } finally { + running = false + } + } + + resolveOnce() + timer = setInterval(resolveOnce, interval) + if (timer.unref) { + timer.unref() + } + + return () => { + if (timer) { + clearInterval(timer) + timer = null + } + } +} + +module.exports = { + startProcessResolver, + parseNetstat, + parseTasklist, +} diff --git a/packages/mitmproxy/test/dnsDefaultLookupTest.mjs b/packages/mitmproxy/test/dnsDefaultLookupTest.mjs new file mode 100644 index 0000000000..709822db8b --- /dev/null +++ b/packages/mitmproxy/test/dnsDefaultLookupTest.mjs @@ -0,0 +1,103 @@ +import assert from 'node:assert/strict' +import { createRequire } from 'node:module' + +const require = createRequire(import.meta.url) + +// 回归测试:Node 20+ 默认开启 autoSelectFamily,会以 { all: true } 调用自定义 lookup, +// 期望回传完整地址数组。旧实现只回传单个 IP 字符串,导致 +// ERR_INVALID_IP_ADDRESS: Invalid IP address: undefined。 +const dnsModulePath = require.resolve('node:dns') +const dnsLookupModulePath = require.resolve('../src/lib/proxy/mitmproxy/dnsLookup.js') + +const IPV6 = '2603:1030:a07:e::102' +const IPV4 = '20.202.1.1' + +function mockDnsLookup (dnsModule) { + const originalLookup = dnsModule.lookup + dnsModule.lookup = (hostname, options, callback) => { + if (options && options.all === true) { + callback(null, [ + { address: IPV6, family: 6 }, + { address: IPV4, family: 4 }, + ]) + } else { + callback(null, IPV4, 4) + } + } + return () => { + dnsModule.lookup = originalLookup + } +} + +function lookupOnce (lookup, options) { + return new Promise((resolve, reject) => { + let callCount = 0 + lookup('example.com', options, (err, address, family) => { + callCount++ + if (callCount > 1) { + reject(new Error('lookup callback 被重复调用')) + return + } + if (err) { + reject(err) + return + } + resolve({ address, family, callCount }) + }) + // 回调是同步触发的,这里兜底防止异步时无限等待 + setTimeout(() => reject(new Error('lookup 回调超时')), 2000) + }) +} + +async function main () { + const dnsModule = require('node:dns') + const restoreDns = mockDnsLookup(dnsModule) + + try { + delete require.cache[dnsLookupModulePath] + const dnsLookup = require(dnsLookupModulePath) + const lookup = dnsLookup.createDefaultLookupFunc(null, 'test', 'target') + + // 1. all: true 时必须回传完整地址数组(修复前的关键回归点) + const allResult = await lookupOnce(lookup, { all: true }) + assert.deepStrictEqual(allResult.address, [ + { address: IPV6, family: 6 }, + { address: IPV4, family: 4 }, + ]) + assert.strictEqual(allResult.callCount, 1) + + // 2. 非 all 模式仍回传单个 IP + family + const singleResult = await lookupOnce(lookup, { family: 4 }) + assert.strictEqual(singleResult.address, IPV4) + assert.strictEqual(singleResult.family, 4) + assert.strictEqual(singleResult.callCount, 1) + + // 3. all 模式解析为空数组时按 ENOTFOUND 处理,不回传空数组 + dnsModule.lookup = (hostname, options, callback) => { + callback(null, []) + } + delete require.cache[dnsLookupModulePath] + const dnsLookup2 = require(dnsLookupModulePath) + const lookup2 = dnsLookup2.createDefaultLookupFunc(null, 'test', 'target') + const emptyResult = await new Promise((resolve) => { + let settled = false + lookup2('example.com', { all: true }, (err, address) => { + if (settled) return + settled = true + resolve({ err, address }) + }) + }) + assert.strictEqual(emptyResult.err.code, 'ENOTFOUND') + assert.strictEqual(emptyResult.address, undefined) + } finally { + restoreDns() + delete require.cache[dnsLookupModulePath] + } + + console.log('dnsDefaultLookupTest passed') +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/mitmproxy/test/ipv6-test.mjs b/packages/mitmproxy/test/ipv6-test.mjs index 9e07016ae8..07f923e7b1 100644 --- a/packages/mitmproxy/test/ipv6-test.mjs +++ b/packages/mitmproxy/test/ipv6-test.mjs @@ -1,22 +1,24 @@ import assert from 'node:assert' -import { isIPv6 } from '../src/lib/dns/util.ip.js' +import utilIp from '../src/lib/dns/util.ip.js' + +const { isIPv6 } = utilIp assert.strictEqual(isIPv6, isIPv6) -assert.strictEqual(isIPv6('2001:0db8:0000:0000:0000:0000:1428:57ab'), true); // true -assert.strictEqual(isIPv6('[2001:0db8:0000:0000:0000:0000:1428:57ab]'), true); // true -assert.strictEqual(isIPv6('[2001:0db8:0000:0000:0000:0000:1428:57ab]:443'), true); // true -assert.strictEqual(isIPv6('http://[2001:0db8:0000:0000:0000:0000:1428:57ab]:443'), true); // true -assert.strictEqual(isIPv6('2001:db8:0:0:0:0:1428:57ab'), true); // true -assert.strictEqual(isIPv6('http://[2001:db8:0:0:0:0:1428:57ab]:443'), true); // true -assert.strictEqual(isIPv6('2001:db8::1428:57ab'), true); // true -assert.strictEqual(isIPv6('http://[2001:db8::1428:57ab]:443'), true); // true -assert.strictEqual(isIPv6('fe80::1'), true); // true -assert.strictEqual(isIPv6('http://[fe80::1]:443'), true); // true -assert.strictEqual(isIPv6('::1'), true); // true -assert.strictEqual(isIPv6('http://[::1]:443'), true); // true -assert.strictEqual(isIPv6('2001:4860:4860::8888'), true); // true -assert.strictEqual(isIPv6('http://[2001:4860:4860::8888]:443'), true); // true -assert.strictEqual(isIPv6('http://example.com'), false); // false +assert.strictEqual(isIPv6('2001:0db8:0000:0000:0000:0000:1428:57ab'), true) // true +assert.strictEqual(isIPv6('[2001:0db8:0000:0000:0000:0000:1428:57ab]'), true) // true +assert.strictEqual(isIPv6('[2001:0db8:0000:0000:0000:0000:1428:57ab]:443'), true) // true +assert.strictEqual(isIPv6('http://[2001:0db8:0000:0000:0000:0000:1428:57ab]:443'), true) // true +assert.strictEqual(isIPv6('2001:db8:0:0:0:0:1428:57ab'), true) // true +assert.strictEqual(isIPv6('http://[2001:db8:0:0:0:0:1428:57ab]:443'), true) // true +assert.strictEqual(isIPv6('2001:db8::1428:57ab'), true) // true +assert.strictEqual(isIPv6('http://[2001:db8::1428:57ab]:443'), true) // true +assert.strictEqual(isIPv6('fe80::1'), true) // true +assert.strictEqual(isIPv6('http://[fe80::1]:443'), true) // true +assert.strictEqual(isIPv6('::1'), true) // true +assert.strictEqual(isIPv6('http://[::1]:443'), true) // true +assert.strictEqual(isIPv6('2001:4860:4860::8888'), true) // true +assert.strictEqual(isIPv6('http://[2001:4860:4860::8888]:443'), true) // true +assert.strictEqual(isIPv6('http://example.com'), false) // false import defaultDns from 'node:dns' import dns from '../src/lib/dns/index.js' @@ -41,7 +43,7 @@ const dnsProviders = dns.initDNS({ server: 'tcp://223.5.5.5', }, TCP2: { - server: "tcp://[2606:4700:4700::1111]", + server: 'tcp://[2606:4700:4700::1111]', }, // udp UDP1: { @@ -60,7 +62,7 @@ const hostname = 'rr4---sn-npoe7nek.gvt1.com' const family = 6 -async function test(dns) { +async function test (dns) { const ip = await dns.lookup(hostname, { family }) console.log(`\n\n【${dns.dnsName} - ${dns.dnsServer}(IPv${dns.dnsFamily})】 ${hostname} -> ${ip}(IPv${family})`, '\n\n') } @@ -74,7 +76,6 @@ await test(dnsProviders.TCP2) await test(dnsProviders.UDP1) await test(dnsProviders.UDP2) -// eslint-disable-next-line node/handle-callback-err defaultDns.lookup(hostname, { family }, (...args) => { console.log(`\n\n【test Default DNS(IPv${family})】 ${hostname} -> ${args[1]}`, '\n\n', args) }) diff --git a/packages/mitmproxy/test/ipv6nettest.js b/packages/mitmproxy/test/ipv6nettest.js new file mode 100644 index 0000000000..b31eb109d6 --- /dev/null +++ b/packages/mitmproxy/test/ipv6nettest.js @@ -0,0 +1,55 @@ +const net = require('net'); +const { setTimeout } = require('timers/promises'); + +// 测试的IPv6地址和端口 +const TEST_HOST = '6.ipw.cn'; +const TEST_PORT = 80; +const TIMEOUT = 5000; // 5秒超时 + +async function testIPv6Connection() { + const socket = new net.Socket(); + + // 设置超时 + socket.setTimeout(TIMEOUT); + + try { + // 尝试连接 + await new Promise((resolve, reject) => { + socket.on('connect', () => { + const { address, port } = socket.address(); + console.log(`成功连接到 ${TEST_HOST} 的IPv6地址 [${address}]:${port}`); + socket.end(); + resolve(); + }); + + socket.on('timeout', () => { + socket.destroy(); + reject(new Error('连接超时')); + }); + + socket.on('error', (err) => { + reject(err); + }); + + socket.connect(TEST_PORT, TEST_HOST); + }); + + return true; + } catch (err) { + console.error('IPv6连接测试失败:', err.message); + return false; + } finally { + socket.destroy(); + } +} + +// 执行测试 +testIPv6Connection() + .then(success => { + console.log(`IPv6连接测试结果: ${success ? '成功' : '失败'}`); + process.exit(success ? 0 : 1); + }) + .catch(err => { + console.error('测试过程中发生错误:', err); + process.exit(1); + }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d01f2eb3bc..d2906c9972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,18 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - axios: '>=1.18.1' - brace-expansion@<2.0.0: 1.1.11 - cross-spawn: '>=7.0.5' - dns-packet: '>=5.6.1' - form-data: '>=4.0.6' - hoek: npm:@hapi/hoek@>=10.0.0 - ip: '>=2.0.1' - minimist: '>=1.2.6' - qs: '>=6.15.3' - tough-cookie: '>=4.1.3 <5.0.0' - importers: .: @@ -106,9 +94,15 @@ importers: '@starknt/shutdown-handler-napi': specifier: ^0.0.3 version: 0.0.3 + '@starknt/shutdown-handler-napi-win32-x64-msvc': + specifier: ^0.0.3 + version: 0.0.3 '@starknt/sysproxy': specifier: ^0.0.3 version: 0.0.3 + '@starknt/sysproxy-win32-x64-msvc': + specifier: ^0.0.3 + version: 0.0.3 '@vscode/sudo-prompt': specifier: ^9.3.2 version: 9.3.2 @@ -140,7 +134,7 @@ importers: specifier: ^4.18.1 version: 4.18.1 minimist: - specifier: '>=1.2.6' + specifier: ^1.2.8 version: 1.2.8 request-progress: specifier: ^3.0.0 @@ -213,7 +207,7 @@ importers: specifier: ^4.6.0 version: 4.6.0 axios: - specifier: '>=1.18.1' + specifier: ^1.18.1 version: 1.18.1(debug@4.4.3(supports-color@4.4.0))(supports-color@4.4.0) baidu-aip-sdk: specifier: ^4.16.16 @@ -1168,9 +1162,6 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hapi/hoek@11.0.7': - resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} - '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -2595,6 +2586,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.6: + resolution: {integrity: sha512-cN6NJ9NnPLDiP/CpmVC1knLFqNjD9Hi1vPsacL/WQP3v8cqVbZpbpX6NHmYJo2fR4B80CgE4cEgPWiDauAQzPw==} + engines: {node: '>= 0.8'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2905,6 +2900,13 @@ packages: engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} hasBin: true + cross-spawn@4.0.2: + resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3151,6 +3153,10 @@ packages: dns-over-http@0.2.0: resolution: {integrity: sha512-K+SyN2L3ljxJ2MFtOv/vRS+3/YEMLvOuH7MrmO5ejaubi4w02/DLqzoK1kBGKlQrT9ND57pbapeDf+ue8AElEA==} + dns-packet@4.2.0: + resolution: {integrity: sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==} + engines: {node: '>=4'} + dns-packet@5.6.1: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} @@ -3312,6 +3318,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -3740,6 +3750,14 @@ packages: forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + form-data@2.3.2: + resolution: {integrity: sha512-6DD2fGWwyxCca2EASUT50GsxWEuwNQDpjMhD9TTaBvI1NE3nLkCr5v7nRdtlmG5g+mNqosdOVHVro+UGmp0Kcw==} + engines: {node: '>= 0.12'} + + form-data@3.0.5: + resolution: {integrity: sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==} + engines: {node: '>= 6'} + form-data@4.0.6: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} @@ -3975,6 +3993,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hoek@6.1.3: + resolution: {integrity: sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==} + deprecated: This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues. + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -4124,6 +4146,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ip@1.1.9: + resolution: {integrity: sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -4232,6 +4257,9 @@ packages: resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} engines: {node: '>= 18.0.0'} + isexe@1.1.1: + resolution: {integrity: sha512-k1YjpQ5+RsFWJ5zY6wKt3ozCKse+HrkOZNg5BwkbB3xrlbKw42V0xZUUwUqGFNvUwW9oKIO4/ejxo83p4MWSgw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -4758,6 +4786,9 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} + minimist@0.0.8: + resolution: {integrity: sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4851,6 +4882,9 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -5440,16 +5474,21 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + qs@6.15.3: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + qs@6.5.5: + resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -5756,10 +5795,18 @@ packages: shallow-equal@1.2.1: resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -6147,9 +6194,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -6267,10 +6314,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6291,9 +6334,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -6487,6 +6527,10 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@1.2.9: + resolution: {integrity: sha512-in9Hb8KabOpB74FlW6nWmxHUQM2s68/alCKLHlisufhiC2c6sopH99skxQn10pW8iL2Az/NfytNUEl0/qHjFdw==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -7720,8 +7764,6 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hapi/hoek@11.0.7': {} - '@hapi/hoek@9.3.0': {} '@hapi/topo@5.1.0': @@ -7931,8 +7973,7 @@ snapshots: '@starknt/shutdown-handler-napi-win32-ia32-msvc@0.0.3': optional: true - '@starknt/shutdown-handler-napi-win32-x64-msvc@0.0.3': - optional: true + '@starknt/shutdown-handler-napi-win32-x64-msvc@0.0.3': {} '@starknt/shutdown-handler-napi@0.0.3': optionalDependencies: @@ -7949,8 +7990,7 @@ snapshots: '@starknt/sysproxy-linux-x64-gnu@0.0.3': optional: true - '@starknt/sysproxy-win32-x64-msvc@0.0.3': - optional: true + '@starknt/sysproxy-win32-x64-msvc@0.0.3': {} '@starknt/sysproxy@0.0.3': optionalDependencies: @@ -8835,7 +8875,7 @@ snapshots: accept@3.1.3: dependencies: boom: 7.3.0 - hoek: '@hapi/hoek@11.0.7' + hoek: 6.1.3 accepts@1.3.8: dependencies: @@ -9226,7 +9266,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.14.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -9245,7 +9285,7 @@ snapshots: boom@7.3.0: dependencies: - hoek: '@hapi/hoek@11.0.7' + hoek: 6.1.3 brace-expansion@1.1.11: dependencies: @@ -9517,6 +9557,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.6: + dependencies: + delayed-stream: 1.0.0 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -9665,6 +9709,19 @@ snapshots: dependencies: cross-spawn: 7.0.6 + cross-spawn@4.0.2: + dependencies: + lru-cache: 4.1.5 + which: 1.2.9 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.2.9 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -9939,7 +9996,7 @@ snapshots: accept: 3.1.3 cors: 2.8.6 debug: 3.2.7(supports-color@4.4.0) - dns-packet: 5.6.1 + dns-packet: 4.2.0 dns-socket: 3.0.0 extend: 3.0.2 lru-cache: 4.1.5 @@ -9948,13 +10005,18 @@ snapshots: transitivePeerDependencies: - supports-color + dns-packet@4.2.0: + dependencies: + ip: 1.1.9 + safe-buffer: 5.2.1 + dns-packet@5.6.1: dependencies: '@leichtgewicht/ip-codec': 2.0.5 dns-socket@3.0.0: dependencies: - dns-packet: 5.6.1 + dns-packet: 4.2.0 dom-align@1.12.4: {} @@ -10147,6 +10209,10 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 @@ -10538,7 +10604,7 @@ snapshots: execa@0.5.1: dependencies: - cross-spawn: 7.0.6 + cross-spawn: 4.0.2 get-stream: 2.3.1 is-stream: 1.1.0 npm-run-path: 2.0.2 @@ -10548,7 +10614,7 @@ snapshots: execa@1.0.0: dependencies: - cross-spawn: 7.0.6 + cross-spawn: 6.0.6 get-stream: 4.1.0 is-stream: 1.1.0 npm-run-path: 2.0.2 @@ -10593,7 +10659,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.15.3 + qs: 6.14.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2(supports-color@8.1.1) @@ -10738,6 +10804,20 @@ snapshots: forever-agent@0.6.1: {} + form-data@2.3.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.6 + mime-types: 2.1.35 + + form-data@3.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + form-data@4.0.6: dependencies: asynckit: 0.4.0 @@ -10807,7 +10887,7 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 @@ -10818,7 +10898,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-stream@2.3.1: dependencies: @@ -10996,6 +11076,8 @@ snapshots: highlight.js@10.7.3: {} + hoek@6.1.3: {} + hosted-git-info@2.8.9: {} hosted-git-info@4.1.0: @@ -11171,6 +11253,8 @@ snapshots: inherits@2.0.4: {} + ip@1.1.9: {} + ipaddr.js@1.9.1: {} ipaddr.js@2.3.0: {} @@ -11241,6 +11325,8 @@ snapshots: isbinaryfile@5.0.7: {} + isexe@1.1.1: {} + isexe@2.0.0: {} isexe@3.1.5: {} @@ -11912,6 +11998,9 @@ snapshots: dependencies: brace-expansion: 2.1.4 + minimist@0.0.8: + optional: true + minimist@1.2.8: {} minipass@3.3.6: @@ -11926,7 +12015,7 @@ snapshots: mkdirp@0.5.1: dependencies: - minimist: 1.2.8 + minimist: 0.0.8 optional: true mkdirp@0.5.6: @@ -12015,6 +12104,8 @@ snapshots: neo-async@2.6.2: {} + nice-try@1.0.5: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -12580,14 +12671,18 @@ snapshots: punycode@2.3.1: {} + qs@6.14.2: + dependencies: + side-channel: 1.1.1 + qs@6.15.3: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 - quansync@0.2.11: {} + qs@6.5.5: {} - querystringify@2.2.0: {} + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -12719,7 +12814,7 @@ snapshots: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.6 + form-data: 2.3.2 har-validator: 5.1.5 http-signature: 1.2.0 is-typedarray: 1.0.0 @@ -12728,9 +12823,9 @@ snapshots: mime-types: 2.1.35 oauth-sign: 0.9.0 performance-now: 2.1.0 - qs: 6.15.3 + qs: 6.5.5 safe-buffer: 5.2.1 - tough-cookie: 4.1.4 + tough-cookie: 2.5.0 tunnel-agent: 0.6.0 uuid: 3.4.0 @@ -12946,10 +13041,16 @@ snapshots: shallow-equal@1.2.1: {} + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} shell-env@0.3.0: @@ -13239,7 +13340,7 @@ snapshots: cookiejar: 2.1.4 debug: 4.4.3(supports-color@8.1.1) fast-safe-stringify: 2.1.1 - form-data: 4.0.6 + form-data: 3.0.5 formidable: 1.2.6 methods: 1.1.2 mime: 2.6.0 @@ -13411,12 +13512,10 @@ snapshots: totalist@3.0.1: {} - tough-cookie@4.1.4: + tough-cookie@2.5.0: dependencies: psl: 1.15.0 punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 tr46@0.0.3: {} @@ -13510,8 +13609,6 @@ snapshots: universalify@0.1.2: {} - universalify@0.2.0: {} - universalify@2.0.1: {} unpipe@1.0.0: {} @@ -13553,11 +13650,6 @@ snapshots: dependencies: punycode: 2.3.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} @@ -13878,6 +13970,10 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which@1.2.9: + dependencies: + isexe: 1.1.1 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 020b5dde24..c67bb70a3c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,18 +6,6 @@ packages: shamefullyHoist: true -overrides: - axios: '>=1.18.1' - brace-expansion@<2.0.0: '1.1.11' - cross-spawn: '>=7.0.5' - dns-packet: '>=5.6.1' - form-data: '>=4.0.6' - hoek: 'npm:@hapi/hoek@>=10.0.0' - ip: '>=2.0.1' - minimist: '>=1.2.6' - qs: '>=6.15.3' - tough-cookie: '>=4.1.3 <5.0.0' - supportedArchitectures: os: - current