From 57eea71b4e6d5a29d4923b97fb12b9b1822243d7 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 7 Aug 2024 15:55:47 +0200 Subject: [PATCH 1/2] fix(devtools-proxy-support): lazily load expensive dependencies Notably, this is referring to `pac-proxy-agent` and `ssh2`, which both involve somewhat complex setups. Also, since `ssh2` includes a WebAssembly implementation of a cryptographic algorithm, we should make sure not to load it in FIPS mode. --- package-lock.json | 214 ++++++++---------- packages/devtools-proxy-support/package.json | 6 +- packages/devtools-proxy-support/src/agent.ts | 2 +- .../devtools-proxy-support/src/proxy-agent.ts | 210 +++++++++++++++++ packages/devtools-proxy-support/src/ssh.ts | 20 +- 5 files changed, 329 insertions(+), 123 deletions(-) create mode 100644 packages/devtools-proxy-support/src/proxy-agent.ts diff --git a/package-lock.json b/package-lock.json index 972eefe8..3319bd6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21087,74 +21087,11 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/psl": { "version": "1.9.0", @@ -26014,8 +25951,12 @@ "dependencies": { "@mongodb-js/socksv5": "^0.0.10", "agent-base": "^7.1.1", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "lru-cache": "^11.0.0", "node-fetch": "^3.3.2", - "proxy-agent": "^6.4.0", + "pac-proxy-agent": "7.0.2", + "socks-proxy-agent": "^8.0.4", "ssh2": "^1.15.0", "system-ca": "^2.0.0" }, @@ -26050,6 +25991,41 @@ "node": ">= 12" } }, + "packages/devtools-proxy-support/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "packages/devtools-proxy-support/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "packages/devtools-proxy-support/node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "packages/devtools-proxy-support/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -26067,6 +26043,20 @@ "url": "https://opencollective.com/node-fetch" } }, + "packages/devtools-proxy-support/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "packages/devtools-proxy-support/node_modules/typescript": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", @@ -32525,12 +32515,16 @@ "electron": "^31.2.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "lru-cache": "^11.0.0", "mocha": "^8.4.0", "node-fetch": "^3.3.2", "nyc": "^15.1.0", + "pac-proxy-agent": "7.0.2", "prettier": "^2.3.2", - "proxy-agent": "^6.4.0", "sinon": "^9.2.3", + "socks-proxy-agent": "^8.0.4", "ssh2": "^1.15.0", "system-ca": "^2.0.0", "typescript": "^5.0.4", @@ -32542,6 +32536,29 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==" + }, "node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -32552,6 +32569,16 @@ "formdata-polyfill": "^4.0.10" } }, + "socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "requires": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, "typescript": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", @@ -45216,60 +45243,11 @@ } } }, - "proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "dependencies": { - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - }, - "socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "requires": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - } - } - } - }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "psl": { "version": "1.9.0", diff --git a/packages/devtools-proxy-support/package.json b/packages/devtools-proxy-support/package.json index 659a483d..dfcf66f3 100644 --- a/packages/devtools-proxy-support/package.json +++ b/packages/devtools-proxy-support/package.json @@ -48,8 +48,12 @@ "dependencies": { "@mongodb-js/socksv5": "^0.0.10", "agent-base": "^7.1.1", + "lru-cache": "^11.0.0", "node-fetch": "^3.3.2", - "proxy-agent": "^6.4.0", + "pac-proxy-agent": "7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "socks-proxy-agent": "^8.0.4", "ssh2": "^1.15.0", "system-ca": "^2.0.0" }, diff --git a/packages/devtools-proxy-support/src/agent.ts b/packages/devtools-proxy-support/src/agent.ts index 30aca5d8..bfc05d23 100644 --- a/packages/devtools-proxy-support/src/agent.ts +++ b/packages/devtools-proxy-support/src/agent.ts @@ -1,4 +1,4 @@ -import { ProxyAgent } from 'proxy-agent'; +import { ProxyAgent } from './proxy-agent'; import type { Agent } from 'https'; import type { DevtoolsProxyOptions } from './proxy-options'; import { proxyForUrl } from './proxy-options'; diff --git a/packages/devtools-proxy-support/src/proxy-agent.ts b/packages/devtools-proxy-support/src/proxy-agent.ts new file mode 100644 index 00000000..8295a237 --- /dev/null +++ b/packages/devtools-proxy-support/src/proxy-agent.ts @@ -0,0 +1,210 @@ +// Copyright (c) 2013 Nathan Rajlich +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// 'Software'), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file is closely adapted from +// https://github.com/TooTallNate/proxy-agents/blob/5555794b6d9e4b0a36fac80a2d3acea876a8f7dc/packages/proxy-agent/src/index.ts +// (hence the license notice above), with core differences being that +// this module uses a different `getProxyForUrl` signature for more flexibility +// and loads the individual agents it defers to lazily. +// Relevant pull requests have been linked in-line. + +import * as http from 'http'; +import * as https from 'https'; +import { URL } from 'url'; +import { LRUCache } from 'lru-cache'; +import type { AgentConnectOpts } from 'agent-base'; +import { Agent } from 'agent-base'; +import createDebug from 'debug'; +import type { PacProxyAgentOptions } from 'pac-proxy-agent'; +import type { PacProxyAgent } from 'pac-proxy-agent'; +import type { HttpProxyAgentOptions } from 'http-proxy-agent'; +import type { HttpProxyAgent } from 'http-proxy-agent'; +import type { HttpsProxyAgentOptions } from 'https-proxy-agent'; +import type { HttpsProxyAgent } from 'https-proxy-agent'; +import type { SocksProxyAgentOptions } from 'socks-proxy-agent'; +import type { SocksProxyAgent } from 'socks-proxy-agent'; + +const debug = createDebug('proxy-agent'); + +type ValidProtocol = + | typeof HttpProxyAgent.protocols[number] + | typeof HttpsProxyAgent.protocols[number] + | typeof SocksProxyAgent.protocols[number] + | typeof PacProxyAgent.protocols[number]; + +type AgentConstructor = new ( + proxy: string, + proxyAgentOptions?: ProxyAgentOptions +) => Agent; + +type GetProxyForUrlCallback = ( + url: string, + req: http.ClientRequest +) => string | Promise; + +/** + * Shorthands for built-in supported types. + */ +// https://github.com/TooTallNate/proxy-agents/pull/327 +const wellKnownAgents = { + http: async () => (await import('http-proxy-agent')).HttpProxyAgent, + https: async () => (await import('https-proxy-agent')).HttpsProxyAgent, + socks: async () => (await import('socks-proxy-agent')).SocksProxyAgent, + pac: async () => (await import('pac-proxy-agent')).PacProxyAgent, +} as const; + +/** + * Supported proxy types. + */ +export const proxies: { + [P in ValidProtocol]: [ + () => Promise, + () => Promise + ]; +} = { + http: [wellKnownAgents.http, wellKnownAgents.https], + https: [wellKnownAgents.http, wellKnownAgents.https], + socks: [wellKnownAgents.socks, wellKnownAgents.socks], + socks4: [wellKnownAgents.socks, wellKnownAgents.socks], + socks4a: [wellKnownAgents.socks, wellKnownAgents.socks], + socks5: [wellKnownAgents.socks, wellKnownAgents.socks], + socks5h: [wellKnownAgents.socks, wellKnownAgents.socks], + 'pac+data': [wellKnownAgents.pac, wellKnownAgents.pac], + 'pac+file': [wellKnownAgents.pac, wellKnownAgents.pac], + 'pac+ftp': [wellKnownAgents.pac, wellKnownAgents.pac], + 'pac+http': [wellKnownAgents.pac, wellKnownAgents.pac], + 'pac+https': [wellKnownAgents.pac, wellKnownAgents.pac], +}; + +function isValidProtocol(v: string): v is ValidProtocol { + return Object.keys(proxies).includes(v); +} + +export type ProxyAgentOptions = HttpProxyAgentOptions<''> & + HttpsProxyAgentOptions<''> & + SocksProxyAgentOptions & + PacProxyAgentOptions<''> & { + /** + * Default `http.Agent` instance to use when no proxy is + * configured for a request. Defaults to a new `http.Agent()` + * instance with the proxy agent options passed in. + */ + httpAgent?: http.Agent; + /** + * Default `http.Agent` instance to use when no proxy is + * configured for a request. Defaults to a new `https.Agent()` + * instance with the proxy agent options passed in. + */ + httpsAgent?: http.Agent; + /** + * A callback for dynamic provision of proxy for url. + * Defaults to standard proxy environment variables, + * see https://www.npmjs.com/package/proxy-from-env for details + */ + // https://github.com/TooTallNate/proxy-agents/pull/326 + getProxyForUrl: GetProxyForUrlCallback; + }; + +/** + * Uses the appropriate `Agent` subclass based off of the "proxy" + * environment variables that are currently set. + * + * An LRU cache is used, to prevent unnecessary creation of proxy + * `http.Agent` instances. + */ +export class ProxyAgent extends Agent { + /** + * Cache for `Agent` instances. + */ + // https://github.com/TooTallNate/proxy-agents/pull/325 + cache = new LRUCache({ + max: 20, + dispose: (agent) => agent.destroy(), + }); + + connectOpts?: ProxyAgentOptions; + httpAgent: http.Agent; + httpsAgent: http.Agent; + getProxyForUrl: GetProxyForUrlCallback; + + constructor(opts: ProxyAgentOptions) { + super(opts); + debug('Creating new ProxyAgent instance: %o', opts); + this.connectOpts = opts; + this.httpAgent = opts?.httpAgent || new http.Agent(opts); + this.httpsAgent = + opts?.httpsAgent || new https.Agent(opts as https.AgentOptions); + this.getProxyForUrl = opts.getProxyForUrl; + } + + async connect( + req: http.ClientRequest, + opts: AgentConnectOpts + ): Promise { + const { secureEndpoint } = opts; + const isWebSocket = req.getHeader('upgrade') === 'websocket'; + const protocol = secureEndpoint + ? isWebSocket + ? 'wss:' + : 'https:' + : isWebSocket + ? 'ws:' + : 'http:'; + const host = req.getHeader('host'); + const url = new URL(req.path, `${protocol}//${String(host)}`).href; + const proxy = await this.getProxyForUrl(url, req); + + if (!proxy) { + debug('Proxy not enabled for URL: %o', url); + return secureEndpoint ? this.httpsAgent : this.httpAgent; + } + + debug('Request URL: %o', url); + debug('Proxy URL: %o', proxy); + + // attempt to get a cached `http.Agent` instance first + const cacheKey = `${protocol}+${proxy}`; + let agent = this.cache.get(cacheKey); + if (!agent) { + const proxyUrl = new URL(proxy); + const proxyProto = proxyUrl.protocol.replace(':', ''); + if (!isValidProtocol(proxyProto)) { + throw new Error(`Unsupported protocol for proxy URL: ${proxy}`); + } + const ctor = await proxies[proxyProto][ + secureEndpoint || isWebSocket ? 1 : 0 + ](); + agent = new ctor(proxy, this.connectOpts); + this.cache.set(cacheKey, agent); + } else { + debug('Cache hit for proxy URL: %o', proxy); + } + + return agent; + } + + destroy(): void { + for (const agent of this.cache.values()) { + agent.destroy(); + } + super.destroy(); + } +} diff --git a/packages/devtools-proxy-support/src/ssh.ts b/packages/devtools-proxy-support/src/ssh.ts index feebc890..3fc8cb4a 100644 --- a/packages/devtools-proxy-support/src/ssh.ts +++ b/packages/devtools-proxy-support/src/ssh.ts @@ -4,14 +4,28 @@ import type { DevtoolsProxyOptions } from './proxy-options'; import type { AgentWithInitialize } from './agent'; import type { ClientRequest } from 'http'; import type { Duplex } from 'stream'; -import type { ClientChannel, ConnectConfig } from 'ssh2'; -import { Client as SshClient } from 'ssh2'; +import type { ClientChannel, ConnectConfig, Client as SshClient } from 'ssh2'; import EventEmitter, { once } from 'events'; import { promises as fs } from 'fs'; import { promisify } from 'util'; import type { ProxyLogEmitter } from './logging'; import { connect as tlsConnect } from 'tls'; import type { Socket } from 'net'; +import { getFips } from 'crypto'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +function ssh2(): typeof import('ssh2') { + if (getFips()) { + // ssh2 uses a WASM implementation of the non-FIPS-compliant Poly1305 hash algorithm + throw new Error( + 'Using `ssh2` features in FIPS mode is currently not available' + ); + } + // Lazily loading this package because it uses WebAssembly and therefore cannot + // be included in startup snapshots, and generally adds unnecessary loading time + // to the application. + return require('ssh2'); +} // The original version of this code was largely taken from // https://github.com/mongodb-js/compass/tree/55a5a608713d7316d158dc66febeb6b114d8b40d/packages/ssh-tunnel/src @@ -38,7 +52,7 @@ export class SSHAgent extends AgentBase implements AgentWithInitialize { this.logger = logger ?? new EventEmitter().setMaxListeners(Infinity); this.proxyOptions = options; this.url = new URL(options.proxy ?? ''); - this.sshClient = new SshClient(); + this.sshClient = new (ssh2().Client)(); this.sshClient.on('close', () => { this.logger.emit('ssh:client-closed'); this.connected = false; From 388c27a3851d5ac733a3e4099572a9c262acd25e Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 7 Aug 2024 17:25:48 +0200 Subject: [PATCH 2/2] fixup: add package name to error message --- packages/devtools-proxy-support/src/ssh.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools-proxy-support/src/ssh.ts b/packages/devtools-proxy-support/src/ssh.ts index 3fc8cb4a..4a1bc216 100644 --- a/packages/devtools-proxy-support/src/ssh.ts +++ b/packages/devtools-proxy-support/src/ssh.ts @@ -18,7 +18,7 @@ function ssh2(): typeof import('ssh2') { if (getFips()) { // ssh2 uses a WASM implementation of the non-FIPS-compliant Poly1305 hash algorithm throw new Error( - 'Using `ssh2` features in FIPS mode is currently not available' + 'devtools-proxy-support: Using `ssh2` features in FIPS mode is currently not available' ); } // Lazily loading this package because it uses WebAssembly and therefore cannot