Some adaptations"

This commit is contained in:
Reinier Criel 2025-11-10 11:17:56 -08:00
parent ca5c1e8869
commit e455828339
4 changed files with 10 additions and 69 deletions

View file

@ -56,6 +56,8 @@ You can check the installed version by running:
safe-chain --version safe-chain --version
``` ```
> **Note:** When using pip or pip3, Safe Chain may need to install a CA certificate in your OS trust store to enable secure MITM protection. This operation requires root (administrator) permissions. You may be prompted for your password when running pip commands for the first time.
## How it works ## How it works
The Aikido Safe Chain works by running a lightweight proxy server that intercepts package downloads from the npm registry and PyPI. When you run npm, npx, yarn, pnpm, pnpx, bun, bunx, `pip`, or `pip3` commands, all package downloads are routed through this local proxy, which verifies packages in real-time against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine. The Aikido Safe Chain works by running a lightweight proxy server that intercepts package downloads from the npm registry and PyPI. When you run npm, npx, yarn, pnpm, pnpx, bun, bunx, `pip`, or `pip3` commands, all package downloads are routed through this local proxy, which verifies packages in real-time against **[Aikido Intel - Open Sources Threat Intelligence](https://intel.aikido.dev/?tab=malware)**. If malware is detected in any package (including deep dependencies), the proxy blocks the download before the malicious code reaches your machine.

View file

@ -3,10 +3,6 @@ import { safeSpawn } from "../../utils/safeSpawn.js";
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js"; import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
import { installSafeChainCA } from "../../registryProxy/certUtils.js"; import { installSafeChainCA } from "../../registryProxy/certUtils.js";
function shouldMockCAInstall() {
return process.env.SAFE_CHAIN_TEST_SKIP_CA_INSTALL === "1";
}
/** /**
* @param {string} command * @param {string} command
* @param {string[]} args * @param {string[]} args
@ -15,10 +11,9 @@ function shouldMockCAInstall() {
*/ */
export async function runPip(command, args) { export async function runPip(command, args) {
try { try {
// Install Safe Chain CA in OS trust store before running pip, unless in test mode // Install Safe Chain CA in OS trust store before running pip
if (!shouldMockCAInstall()) { // Py 3.14 requires that certs are properly installed in the OS trust store
await installSafeChainCA(); await installSafeChainCA();
}
const env = mergeSafeChainProxyEnvironmentVariables(process.env); const env = mergeSafeChainProxyEnvironmentVariables(process.env);
const result = await safeSpawn(command, args, { const result = await safeSpawn(command, args, {
stdio: "inherit", stdio: "inherit",

View file

@ -36,67 +36,10 @@ describe("runPipCommand environment variable handling", () => {
mock.reset(); mock.reset();
}); });
it("should set REQUESTS_CA_BUNDLE and SSL_CERT_FILE for default PyPI (no explicit index)", async () => {
const res = await runPip("pip3", ["install", "requests"]);
assert.strictEqual(res.status, 0);
assert.ok(capturedArgs, "safeSpawn should have been called");
// Check environment variables are set
assert.strictEqual(
capturedArgs.options.env.REQUESTS_CA_BUNDLE,
"/tmp/test-combined-ca.pem",
"REQUESTS_CA_BUNDLE should be set to combined bundle path"
);
assert.strictEqual(
capturedArgs.options.env.SSL_CERT_FILE,
"/tmp/test-combined-ca.pem",
"SSL_CERT_FILE should be set to combined bundle path"
);
// Args should be unchanged (no arg injection)
assert.deepStrictEqual(capturedArgs.args, ["install", "requests"]);
});
it("should set CA environment variables even for external/test PyPI mirror (covers non-CLI traffic)", async () => {
const res = await runPip("pip3", [
"install",
"certifi",
"--index-url",
"https://test.pypi.org/simple",
]);
assert.strictEqual(res.status, 0);
// Env vars should be set unconditionally
assert.strictEqual(
capturedArgs.options.env.REQUESTS_CA_BUNDLE,
"/tmp/test-combined-ca.pem"
);
assert.strictEqual(
capturedArgs.options.env.SSL_CERT_FILE,
"/tmp/test-combined-ca.pem"
);
});
it("should still set CA env vars for PyPI even with user --cert flag", async () => {
// For default PyPI, we still set env vars; pip CLI --cert takes precedence
const res = await runPip("pip3", ["install", "requests"]);
assert.strictEqual(res.status, 0);
// Environment variables still set (pip CLI --cert takes precedence)
assert.strictEqual(
capturedArgs.options.env.REQUESTS_CA_BUNDLE,
"/tmp/test-combined-ca.pem"
);
assert.strictEqual(
capturedArgs.options.env.SSL_CERT_FILE,
"/tmp/test-combined-ca.pem"
);
});
it("should preserve HTTPS_PROXY from proxy merge", async () => { it("should preserve HTTPS_PROXY from proxy merge", async () => {
const res = await runPip("pip3", ["install", "requests"]); const res = await runPip("pip3", ["install", "requests"]);
assert.strictEqual(res.status, 0); assert.strictEqual(res.status, 0);
assert.strictEqual( assert.strictEqual(
capturedArgs.options.env.HTTPS_PROXY, capturedArgs.options.env.HTTPS_PROXY,
"http://localhost:8080", "http://localhost:8080",

View file

@ -4,6 +4,7 @@ import fs from "fs";
import os from "os"; import os from "os";
import { safeSpawn } from "../utils/safeSpawn.js"; import { safeSpawn } from "../utils/safeSpawn.js";
import { DARWIN_CA_PATH, LINUX_CA_PATH } from "../config/settings.js"; import { DARWIN_CA_PATH, LINUX_CA_PATH } from "../config/settings.js";
import { ui } from "../environment/userInteraction.js";
const certFolder = path.join(os.homedir(), ".safe-chain", "certs"); const certFolder = path.join(os.homedir(), ".safe-chain", "certs");
const ca = loadCa(); const ca = loadCa();
@ -155,7 +156,7 @@ export async function installSafeChainCA() {
try { try {
const alreadyInstalled = await isSafeChainCAInstalled(); const alreadyInstalled = await isSafeChainCAInstalled();
if (alreadyInstalled) { if (alreadyInstalled) {
console.log("Safe Chain CA already installed in OS trust store."); ui.writeVerbose("Safe-chain: CA already installed in OS trust store.");
return; return;
} }
if (platform === "darwin") { if (platform === "darwin") {
@ -171,9 +172,9 @@ export async function installSafeChainCA() {
} else { } else {
throw new Error("Unsupported OS for automatic CA installation. Please install manually."); throw new Error("Unsupported OS for automatic CA installation. Please install manually.");
} }
console.log("Safe Chain CA installed in OS trust store."); ui.writeVerbose("Safe-chain: CA installed in OS trust store.");
} catch (/** @type any */ error) { } catch (/** @type any */ error) {
console.error("Failed to install Safe Chain CA:", error.message); ui.writeError("Failed to install safe-chain: CA:", error.message);
throw error; throw error;
} }
} }