mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Fix tests and add command support
This commit is contained in:
parent
b9de94f0f1
commit
d2fc531c81
14 changed files with 198 additions and 462 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "./pnpm/createPackageManager.js";
|
||||
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
||||
import { createPipPackageManager } from "./pip/createPackageManager.js";
|
||||
import { createPipXPackageManager } from "./pipx/createPipXPackageManager.js";
|
||||
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
|
||||
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function createPipXPackageManager() {
|
|||
runCommand: (args) => {
|
||||
return runPipX("pipx", args);
|
||||
},
|
||||
// For uv, rely solely on MITM
|
||||
// MITM only
|
||||
isSupportedCommand: () => false,
|
||||
getDependencyUpdatesForCommand: () => [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ 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");
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function setPipXCaBundleEnvironmentVariables(env, combinedCaPath) {
|
|||
}
|
||||
env.SSL_CERT_FILE = combinedCaPath;
|
||||
|
||||
// REQUESTS_CA_BUNDLE: Used by the requests library (which uv may use internally)
|
||||
// REQUESTS_CA_BUNDLE: Used by the requests library (may be used by tooling under pipx)
|
||||
if (env.REQUESTS_CA_BUNDLE) {
|
||||
ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
|
||||
}
|
||||
|
|
@ -30,18 +30,11 @@ function setPipXCaBundleEnvironmentVariables(env, combinedCaPath) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Runs a uv command with safe-chain's certificate bundle and proxy configuration.
|
||||
* Runs a pipx command with safe-chain's certificate bundle and proxy configuration.
|
||||
*
|
||||
* uv respects standard environment variables for proxy and TLS configuration:
|
||||
* - HTTP_PROXY / HTTPS_PROXY: Proxy settings
|
||||
* - SSL_CERT_FILE / REQUESTS_CA_BUNDLE: CA bundle for TLS verification
|
||||
*
|
||||
* Unlike pip (which requires a temporary config file for cert configuration), uv directly
|
||||
* honors environment variables, so no config/ini file is needed.
|
||||
*
|
||||
* @param {string} command - The pipx command to execute
|
||||
* @param {string[]} args - Command line arguments to pass to pipx
|
||||
* @returns {Promise<{status: number}>} Exit status of the pipx command
|
||||
* @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 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
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");
|
||||
});
|
||||
|
||||
it("overwrites user CA env vars and warns", async () => {
|
||||
mergedEnvReturn = {
|
||||
HTTPS_PROXY: "http://localhost:8080",
|
||||
HTTP_PROXY: "",
|
||||
SSL_CERT_FILE: "user-ssl",
|
||||
REQUESTS_CA_BUNDLE: "user-requests",
|
||||
PIP_CERT: "user-pip",
|
||||
};
|
||||
|
||||
await runPipX("pipx", ["install", "ruff"]);
|
||||
|
||||
const [, , options] = safeSpawnMock.mock.calls[0].arguments;
|
||||
const env = options.env;
|
||||
|
||||
assert.strictEqual(env.SSL_CERT_FILE, "/tmp/test-combined-ca.pem", "SSL cert should be overwritten");
|
||||
assert.strictEqual(env.REQUESTS_CA_BUNDLE, "/tmp/test-combined-ca.pem", "requests bundle should be overwritten");
|
||||
assert.strictEqual(env.PIP_CERT, "/tmp/test-combined-ca.pem", "pip cert should be overwritten");
|
||||
assert.strictEqual(warnMock.mock.calls.length, 3, "should warn for each overwritten var");
|
||||
});
|
||||
});
|
||||
|
|
@ -98,7 +98,7 @@ export const knownAikidoTools = [
|
|||
tool: "pipx",
|
||||
aikidoCommand: "aikido-pipx",
|
||||
ecoSystem: ECOSYSTEM_PY,
|
||||
internalPackageManagerName: "pip",
|
||||
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