diff --git a/packages/safe-chain/src/packagemanager/pip/runPipCommand.js b/packages/safe-chain/src/packagemanager/pip/runPipCommand.js index 2e8bcbf..5024655 100644 --- a/packages/safe-chain/src/packagemanager/pip/runPipCommand.js +++ b/packages/safe-chain/src/packagemanager/pip/runPipCommand.js @@ -20,29 +20,39 @@ export async function runPip(command, args) { // so that any network request made by pip, including those outside explicit CLI args, // validates correctly under both MITM'd and tunneled HTTPS. const combinedCaPath = getCombinedCaBundlePath(); - env.REQUESTS_CA_BUNDLE = combinedCaPath; - env.SSL_CERT_FILE = combinedCaPath; + if (!env.REQUESTS_CA_BUNDLE) { + env.REQUESTS_CA_BUNDLE = combinedCaPath; + } + + if (!env.SSL_CERT_FILE) { + env.SSL_CERT_FILE = combinedCaPath; + } + // To counter behavior that is sometimes seen where pip ignores REQUESTS_CA_BUNDLE/SSL_CERT_FILE, // We will set additional env vars for pip - env.PIP_CERT = combinedCaPath; + if (!env.PIP_CERT) { + env.PIP_CERT = combinedCaPath; + } - // Create a temporary pip config file - const tmpDir = os.tmpdir(); - const pipConfigPath = path.join(tmpDir, `safe-chain-pip-${Date.now()}.ini`); + // Only create and set PIP_CONFIG_FILE if not already set + if (!env.PIP_CONFIG_FILE) { + const tmpDir = os.tmpdir(); + const pipConfigPath = path.join(tmpDir, `safe-chain-pip-${Date.now()}.ini`); - // Proxy settings - const httpProxy = env.HTTP_PROXY || ''; - const httpsProxy = env.HTTPS_PROXY || ''; + // Proxy settings + const httpProxy = env.HTTP_PROXY || ''; + const httpsProxy = env.HTTPS_PROXY || ''; - // Build pip config INI - let pipConfig = '[global]\n'; - pipConfig += `cert = ${combinedCaPath}\n`; - if (httpProxy) pipConfig += `proxy = ${httpProxy}\n`; - if (httpsProxy) pipConfig += `proxy = ${httpsProxy}\n`; + // Build pip config INI + let pipConfig = '[global]\n'; + pipConfig += `cert = ${combinedCaPath}\n`; + if (httpProxy) pipConfig += `proxy = ${httpProxy}\n`; + if (httpsProxy) pipConfig += `proxy = ${httpsProxy}\n`; - await fs.writeFile(pipConfigPath, pipConfig); - env.PIP_CONFIG_FILE = pipConfigPath; + await fs.writeFile(pipConfigPath, pipConfig); + env.PIP_CONFIG_FILE = pipConfigPath; + } const result = await safeSpawn(command, args, { stdio: "inherit", diff --git a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js index 9d330da..7128bde 100644 --- a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js +++ b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js @@ -4,6 +4,7 @@ import assert from "node:assert"; describe("runPipCommand environment variable handling", () => { let runPip; let capturedArgs = null; + let customEnv = null; beforeEach(async () => { capturedArgs = null; @@ -18,11 +19,12 @@ describe("runPipCommand environment variable handling", () => { }, }); - // Mock proxy env merge + // Mock proxy env merge, allow custom env override mock.module("../../registryProxy/registryProxy.js", { namedExports: { mergeSafeChainProxyEnvironmentVariables: (env) => ({ ...env, + ...(customEnv || {}), HTTPS_PROXY: "http://localhost:8080", }), }, @@ -43,6 +45,25 @@ describe("runPipCommand environment variable handling", () => { mock.reset(); }); + it("should not overwrite existing env vars for certs and config", async () => { + // Set custom env vars before merge + customEnv = { + REQUESTS_CA_BUNDLE: "/custom/ca-bundle.pem", + SSL_CERT_FILE: "/custom/ssl-cert.pem", + PIP_CERT: "/custom/pip-cert.pem", + PIP_CONFIG_FILE: "/custom/pip.conf" + }; + const res = await runPip("pip3", ["install", "requests"]); + assert.strictEqual(res.status, 0); + assert.ok(capturedArgs, "safeSpawn should have been called"); + // Should preserve custom env vars + assert.strictEqual(capturedArgs.options.env.REQUESTS_CA_BUNDLE, "/custom/ca-bundle.pem"); + assert.strictEqual(capturedArgs.options.env.SSL_CERT_FILE, "/custom/ssl-cert.pem"); + assert.strictEqual(capturedArgs.options.env.PIP_CERT, "/custom/pip-cert.pem"); + assert.strictEqual(capturedArgs.options.env.PIP_CONFIG_FILE, "/custom/pip.conf"); + customEnv = null; + }); + it("should set PIP_CERT env var and create config file", async () => { const res = await runPip("pip3", ["install", "requests"]); assert.strictEqual(res.status, 0);