mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
commit
bbf5f8189b
15 changed files with 425 additions and 11 deletions
16
packages/safe-chain/bin/aikido-pipx.js
Executable file
16
packages/safe-chain/bin/aikido-pipx.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { main } from "../src/main.js";
|
||||
import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js";
|
||||
import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js";
|
||||
|
||||
// Set eco system
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
|
||||
initializePackageManager("pipx");
|
||||
|
||||
(async () => {
|
||||
// Pass through only user-supplied pipx args
|
||||
var exitCode = await main(process.argv.slice(2));
|
||||
process.exit(exitCode);
|
||||
})();
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
"aikido-python": "bin/aikido-python.js",
|
||||
"aikido-python3": "bin/aikido-python3.js",
|
||||
"aikido-poetry": "bin/aikido-poetry.js",
|
||||
"aikido-pipx": "bin/aikido-pipx.js",
|
||||
"safe-chain": "bin/safe-chain.js"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
|||
import { createPipPackageManager } from "./pip/createPackageManager.js";
|
||||
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
|
||||
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
|
||||
import { createPipXPackageManager } from "./pipx/createPipXPackageManager.js";
|
||||
|
||||
/**
|
||||
* @type {{packageManagerName: PackageManager | null}}
|
||||
|
|
@ -61,6 +62,8 @@ export function initializePackageManager(packageManagerName, context) {
|
|||
state.packageManagerName = createUvPackageManager();
|
||||
} else if (packageManagerName === "poetry") {
|
||||
state.packageManagerName = createPoetryPackageManager();
|
||||
} else if (packageManagerName === "pipx") {
|
||||
state.packageManagerName = createPipXPackageManager();
|
||||
} else {
|
||||
throw new Error("Unsupported package manager: " + packageManagerName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { runPipX } from "./runPipXCommand.js";
|
||||
|
||||
/**
|
||||
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||
*/
|
||||
export function createPipXPackageManager() {
|
||||
return {
|
||||
/**
|
||||
* @param {string[]} args
|
||||
*/
|
||||
runCommand: (args) => {
|
||||
return runPipX("pipx", args);
|
||||
},
|
||||
// MITM only
|
||||
isSupportedCommand: () => false,
|
||||
getDependencyUpdatesForCommand: () => [],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { createPipXPackageManager } from "./createPipXPackageManager.js";
|
||||
|
||||
test("createPipXPackageManager", async (t) => {
|
||||
await t.test("should create package manager with required interface", () => {
|
||||
const pm = createPipXPackageManager();
|
||||
|
||||
assert.ok(pm);
|
||||
assert.strictEqual(typeof pm.runCommand, "function");
|
||||
assert.strictEqual(typeof pm.isSupportedCommand, "function");
|
||||
assert.strictEqual(typeof pm.getDependencyUpdatesForCommand, "function");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { ui } from "../../environment/userInteraction.js";
|
||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||
|
||||
/**
|
||||
* Sets CA bundle environment variables used by Python libraries and pipx.
|
||||
*
|
||||
* @param {NodeJS.ProcessEnv} env - Env object
|
||||
* @param {string} combinedCaPath - Path to the combined CA bundle
|
||||
* @return {NodeJS.ProcessEnv} Modified environment object
|
||||
*/
|
||||
function getPipXCaBundleEnvironmentVariables(env, combinedCaPath) {
|
||||
let retVal = { ...env };
|
||||
|
||||
if (env.SSL_CERT_FILE) {
|
||||
ui.writeWarning("Safe-chain: User defined SSL_CERT_FILE found in environment. It will be overwritten.");
|
||||
}
|
||||
retVal.SSL_CERT_FILE = combinedCaPath;
|
||||
|
||||
if (env.REQUESTS_CA_BUNDLE) {
|
||||
ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
|
||||
}
|
||||
retVal.REQUESTS_CA_BUNDLE = combinedCaPath;
|
||||
|
||||
if (env.PIP_CERT) {
|
||||
ui.writeWarning("Safe-chain: User defined PIP_CERT found in environment. It will be overwritten.");
|
||||
}
|
||||
retVal.PIP_CERT = combinedCaPath;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a pipx command with safe-chain's certificate bundle and proxy configuration.
|
||||
*
|
||||
* @param {string} command - The command to execute
|
||||
* @param {string[]} args - Command line arguments
|
||||
* @returns {Promise<{status: number}>} Exit status of the command
|
||||
*/
|
||||
export async function runPipX(command, args) {
|
||||
try {
|
||||
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
||||
|
||||
const combinedCaPath = getCombinedCaBundlePath();
|
||||
const modifiedEnv = getPipXCaBundleEnvironmentVariables(env, combinedCaPath);
|
||||
|
||||
// Note: pipx uses HTTPS_PROXY and HTTP_PROXY environment variables for proxy configuration
|
||||
// These are already set by mergeSafeChainProxyEnvironmentVariables
|
||||
|
||||
const result = await safeSpawn(command, args, {
|
||||
stdio: "inherit",
|
||||
env: modifiedEnv,
|
||||
});
|
||||
|
||||
return { status: result.status };
|
||||
} catch (/** @type any */ error) {
|
||||
if (error.status) {
|
||||
return { status: error.status };
|
||||
} else {
|
||||
ui.writeError(`Error executing command: ${error.message}`);
|
||||
ui.writeError(`Is '${command}' installed and available on your system?`);
|
||||
return { status: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||
import assert from "node:assert";
|
||||
|
||||
describe("runPipXCommand", () => {
|
||||
let runPipX;
|
||||
let safeSpawnMock;
|
||||
let warnMock;
|
||||
let errorMock;
|
||||
let mergeCalls;
|
||||
let mergedEnvReturn;
|
||||
|
||||
beforeEach(async () => {
|
||||
mergeCalls = [];
|
||||
mergedEnvReturn = {
|
||||
HTTPS_PROXY: "http://localhost:8080",
|
||||
HTTP_PROXY: "",
|
||||
};
|
||||
|
||||
safeSpawnMock = mock.fn(async () => ({ status: 0 }));
|
||||
warnMock = mock.fn();
|
||||
errorMock = mock.fn();
|
||||
|
||||
mock.module("../../environment/userInteraction.js", {
|
||||
namedExports: {
|
||||
ui: {
|
||||
writeWarning: warnMock,
|
||||
writeError: errorMock,
|
||||
writeInfo: () => {},
|
||||
writeVerbose: () => {},
|
||||
writeSuccess: () => {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mock.module("../../registryProxy/registryProxy.js", {
|
||||
namedExports: {
|
||||
mergeSafeChainProxyEnvironmentVariables: (env) => {
|
||||
mergeCalls.push(env);
|
||||
return { ...env, ...mergedEnvReturn };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mock.module("../../registryProxy/certBundle.js", {
|
||||
namedExports: {
|
||||
getCombinedCaBundlePath: () => "/tmp/test-combined-ca.pem",
|
||||
},
|
||||
});
|
||||
|
||||
mock.module("../../utils/safeSpawn.js", {
|
||||
namedExports: {
|
||||
safeSpawn: safeSpawnMock,
|
||||
},
|
||||
});
|
||||
|
||||
const mod = await import("./runPipXCommand.js");
|
||||
runPipX = mod.runPipX;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
it("sets CA env vars and proxies before spawning", async () => {
|
||||
const res = await runPipX("pipx", ["install", "ruff"]);
|
||||
|
||||
assert.strictEqual(res.status, 0);
|
||||
assert.strictEqual(safeSpawnMock.mock.calls.length, 1, "safeSpawn should be called once");
|
||||
|
||||
const [, , options] = safeSpawnMock.mock.calls[0].arguments;
|
||||
const env = options.env;
|
||||
|
||||
assert.strictEqual(env.SSL_CERT_FILE, "/tmp/test-combined-ca.pem");
|
||||
assert.strictEqual(env.REQUESTS_CA_BUNDLE, "/tmp/test-combined-ca.pem");
|
||||
assert.strictEqual(env.PIP_CERT, "/tmp/test-combined-ca.pem");
|
||||
assert.strictEqual(env.HTTPS_PROXY, "http://localhost:8080");
|
||||
assert.strictEqual(env.HTTP_PROXY, "");
|
||||
assert.ok(mergeCalls.length >= 1, "proxy merge should be invoked");
|
||||
});
|
||||
});
|
||||
|
|
@ -94,6 +94,12 @@ export const knownAikidoTools = [
|
|||
ecoSystem: ECOSYSTEM_PY,
|
||||
internalPackageManagerName: "pip",
|
||||
},
|
||||
{
|
||||
tool: "pipx",
|
||||
aikidoCommand: "aikido-pipx",
|
||||
ecoSystem: ECOSYSTEM_PY,
|
||||
internalPackageManagerName: "pipx",
|
||||
}
|
||||
// When adding a new tool here, also update the documentation for the new tool in the README.md
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ function npm
|
|||
wrapSafeChainCommand "npm" $argv
|
||||
end
|
||||
|
||||
|
||||
function pip
|
||||
wrapSafeChainCommand "pip" $argv
|
||||
end
|
||||
|
|
@ -66,6 +65,10 @@ function python3
|
|||
wrapSafeChainCommand "python3" $argv
|
||||
end
|
||||
|
||||
function pipx
|
||||
wrapSafeChainCommand "pipx" $argv
|
||||
end
|
||||
|
||||
function printSafeChainWarning
|
||||
set original_cmd $argv[1]
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ function npm() {
|
|||
wrapSafeChainCommand "npm" "$@"
|
||||
}
|
||||
|
||||
|
||||
function pip() {
|
||||
wrapSafeChainCommand "pip" "$@"
|
||||
}
|
||||
|
|
@ -62,6 +61,10 @@ function python3() {
|
|||
wrapSafeChainCommand "python3" "$@"
|
||||
}
|
||||
|
||||
function pipx() {
|
||||
wrapSafeChainCommand "pipx" "$@"
|
||||
}
|
||||
|
||||
function printSafeChainWarning() {
|
||||
# \033[43;30m is used to set the background color to yellow and text color to black
|
||||
# \033[0m is used to reset the text formatting
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ function python3 {
|
|||
Invoke-WrappedCommand 'python3' $args
|
||||
}
|
||||
|
||||
function pipx {
|
||||
Invoke-WrappedCommand "pipx" $args
|
||||
}
|
||||
|
||||
function Write-SafeChainWarning {
|
||||
param([string]$Command)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue