mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Skeleton
This commit is contained in:
parent
a57c37b58d
commit
4bfc315b57
12 changed files with 505 additions and 2 deletions
12
packages/safe-chain/bin/aikido-poetry.js
Normal file
12
packages/safe-chain/bin/aikido-poetry.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#!/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";
|
||||
|
||||
setEcoSystem(ECOSYSTEM_PY);
|
||||
const packageManagerName = "poetry";
|
||||
initializePackageManager(packageManagerName);
|
||||
var exitCode = await main(process.argv.slice(2));
|
||||
|
||||
process.exit(exitCode);
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
"aikido-pip3": "bin/aikido-pip3.js",
|
||||
"aikido-python": "bin/aikido-python.js",
|
||||
"aikido-python3": "bin/aikido-python3.js",
|
||||
"aikido-poetry": "bin/aikido-poetry.js",
|
||||
"safe-chain": "bin/safe-chain.js"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { createYarnPackageManager } from "./yarn/createPackageManager.js";
|
||||
import { createPipPackageManager } from "./pip/createPackageManager.js";
|
||||
import { createUvPackageManager } from "./uv/createUvPackageManager.js";
|
||||
import { createPoetryPackageManager } from "./poetry/createPoetryPackageManager.js";
|
||||
|
||||
/**
|
||||
* @type {{packageManagerName: PackageManager | null}}
|
||||
|
|
@ -57,6 +58,8 @@ export function initializePackageManager(packageManagerName) {
|
|||
state.packageManagerName = createPipPackageManager();
|
||||
} else if (packageManagerName === "uv") {
|
||||
state.packageManagerName = createUvPackageManager();
|
||||
} else if (packageManagerName === "poetry") {
|
||||
state.packageManagerName = createPoetryPackageManager();
|
||||
} else {
|
||||
throw new Error("Unsupported package manager: " + packageManagerName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import { ui } from "../../environment/userInteraction.js";
|
||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||
|
||||
/**
|
||||
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||
*/
|
||||
export function createPoetryPackageManager() {
|
||||
return {
|
||||
runCommand: (args) => runPoetryCommand(args),
|
||||
|
||||
// For poetry, we use the proxy-only approach to block package downloads,
|
||||
// so we don't need to analyze commands.
|
||||
isSupportedCommand: () => false,
|
||||
getDependencyUpdatesForCommand: () => [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CA bundle environment variables used by Poetry and Python libraries.
|
||||
* Poetry uses the Python requests library which respects these environment variables.
|
||||
*
|
||||
* @param {NodeJS.ProcessEnv} env - Environment object to modify
|
||||
* @param {string} combinedCaPath - Path to the combined CA bundle
|
||||
*/
|
||||
function setPoetryCaBundleEnvironmentVariables(env, combinedCaPath) {
|
||||
// SSL_CERT_FILE: Used by Python SSL libraries and requests
|
||||
if (env.SSL_CERT_FILE) {
|
||||
ui.writeWarning("Safe-chain: User defined SSL_CERT_FILE found in environment. It will be overwritten.");
|
||||
}
|
||||
env.SSL_CERT_FILE = combinedCaPath;
|
||||
|
||||
// REQUESTS_CA_BUNDLE: Used by the requests library (which Poetry uses)
|
||||
if (env.REQUESTS_CA_BUNDLE) {
|
||||
ui.writeWarning("Safe-chain: User defined REQUESTS_CA_BUNDLE found in environment. It will be overwritten.");
|
||||
}
|
||||
env.REQUESTS_CA_BUNDLE = combinedCaPath;
|
||||
|
||||
// PIP_CERT: Poetry may use pip internally
|
||||
if (env.PIP_CERT) {
|
||||
ui.writeWarning("Safe-chain: User defined PIP_CERT found in environment. It will be overwritten.");
|
||||
}
|
||||
env.PIP_CERT = combinedCaPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a poetry command with safe-chain's certificate bundle and proxy configuration.
|
||||
*
|
||||
* Poetry respects standard HTTP_PROXY/HTTPS_PROXY environment variables through
|
||||
* the Python requests library.
|
||||
*
|
||||
* @param {string[]} args - Command line arguments to pass to poetry
|
||||
* @returns {Promise<{status: number}>} Exit status of the poetry command
|
||||
*/
|
||||
async function runPoetryCommand(args) {
|
||||
try {
|
||||
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
|
||||
|
||||
const combinedCaPath = getCombinedCaBundlePath();
|
||||
setPoetryCaBundleEnvironmentVariables(env, combinedCaPath);
|
||||
|
||||
const result = await safeSpawn("poetry", args, {
|
||||
stdio: "inherit",
|
||||
env,
|
||||
});
|
||||
|
||||
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 'poetry' installed and available on your system?");
|
||||
return { status: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { createPoetryPackageManager } from "./createPoetryPackageManager.js";
|
||||
|
||||
test("createPoetryPackageManager", async (t) => {
|
||||
await t.test("should create package manager with required interface", () => {
|
||||
const pm = createPoetryPackageManager();
|
||||
|
||||
assert.ok(pm);
|
||||
assert.strictEqual(typeof pm.runCommand, "function");
|
||||
assert.strictEqual(typeof pm.isSupportedCommand, "function");
|
||||
assert.strictEqual(typeof pm.getDependencyUpdatesForCommand, "function");
|
||||
});
|
||||
});
|
||||
|
|
@ -36,10 +36,19 @@ function getSafeChainProxyEnvironmentVariables() {
|
|||
return {};
|
||||
}
|
||||
|
||||
const proxyUrl = `http://127.0.0.1:${state.port}`;
|
||||
return {
|
||||
HTTPS_PROXY: `http://localhost:${state.port}`,
|
||||
GLOBAL_AGENT_HTTP_PROXY: `http://localhost:${state.port}`,
|
||||
// Uppercase variants (standard)
|
||||
HTTP_PROXY: proxyUrl,
|
||||
HTTPS_PROXY: proxyUrl,
|
||||
GLOBAL_AGENT_HTTP_PROXY: proxyUrl,
|
||||
NODE_EXTRA_CA_CERTS: getCaCertPath(),
|
||||
// Lowercase variants (some tools like Poetry/requests prefer these)
|
||||
http_proxy: proxyUrl,
|
||||
https_proxy: proxyUrl,
|
||||
// Clear NO_PROXY to ensure all requests go through our proxy
|
||||
NO_PROXY: "",
|
||||
no_proxy: "",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export const knownAikidoTools = [
|
|||
{ tool: "uv", aikidoCommand: "aikido-uv", ecoSystem: ECOSYSTEM_PY },
|
||||
{ tool: "pip", aikidoCommand: "aikido-pip", ecoSystem: ECOSYSTEM_PY },
|
||||
{ tool: "pip3", aikidoCommand: "aikido-pip3", ecoSystem: ECOSYSTEM_PY },
|
||||
{ tool: "poetry", aikidoCommand: "aikido-poetry", ecoSystem: ECOSYSTEM_PY },
|
||||
{ tool: "python", aikidoCommand: "aikido-python", ecoSystem: ECOSYSTEM_PY },
|
||||
{ tool: "python3", aikidoCommand: "aikido-python3", ecoSystem: ECOSYSTEM_PY },
|
||||
// When adding a new tool here, also update the documentation for the new tool in the README.md
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ function uv
|
|||
wrapSafeChainCommand "uv" "aikido-uv" $argv
|
||||
end
|
||||
|
||||
function poetry
|
||||
wrapSafeChainCommand "poetry" "aikido-poetry" $argv
|
||||
end
|
||||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python
|
||||
wrapSafeChainCommand "python" "aikido-python" $argv
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ function uv() {
|
|||
wrapSafeChainCommand "uv" "aikido-uv" "$@"
|
||||
}
|
||||
|
||||
function poetry() {
|
||||
wrapSafeChainCommand "poetry" "aikido-poetry" "$@"
|
||||
}
|
||||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python() {
|
||||
wrapSafeChainCommand "python" "aikido-python" "$@"
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@ function uv {
|
|||
Invoke-WrappedCommand "uv" "aikido-uv" $args
|
||||
}
|
||||
|
||||
function poetry {
|
||||
Invoke-WrappedCommand "poetry" "aikido-poetry" $args
|
||||
}
|
||||
|
||||
# `python -m pip`, `python -m pip3`.
|
||||
function python {
|
||||
Invoke-WrappedCommand 'python' 'aikido-python' $args
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ EOF
|
|||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
||||
echo 'source $HOME/.local/bin/env' >> ~/.bashrc
|
||||
|
||||
# Install Poetry
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 - && \
|
||||
echo 'export PATH="/root/.local/bin:$PATH"' >> ~/.bashrc && \
|
||||
/root/.local/bin/poetry config virtualenvs.in-project true
|
||||
|
||||
# Copy and install Safe chain
|
||||
COPY --from=builder /app/*.tgz /pkgs/
|
||||
RUN npm install -g /pkgs/*.tgz
|
||||
|
|
|
|||
368
test/e2e/poetry.e2e.spec.js
Normal file
368
test/e2e/poetry.e2e.spec.js
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
import { describe, it, before, beforeEach, afterEach } from "node:test";
|
||||
import { DockerTestContainer } from "./DockerTestContainer.js";
|
||||
import assert from "node:assert";
|
||||
|
||||
describe("E2E: poetry coverage", () => {
|
||||
let container;
|
||||
|
||||
before(async () => {
|
||||
DockerTestContainer.buildImage();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Run a new Docker container for each test
|
||||
container = new DockerTestContainer();
|
||||
await container.start();
|
||||
|
||||
const installationShell = await container.openShell("zsh");
|
||||
await installationShell.runCommand("safe-chain setup --include-python");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Stop and clean up the container after each test
|
||||
if (container) {
|
||||
await container.stop();
|
||||
container = null;
|
||||
}
|
||||
});
|
||||
|
||||
it(`successfully installs known safe packages with poetry add`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
// Clear poetry cache using command to bypass safe-chain wrapper
|
||||
await shell.runCommand("command poetry cache clear pypi --all -n");
|
||||
|
||||
// Initialize a new poetry project
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-project && cd /tmp/test-poetry-project");
|
||||
await shell.runCommand("cd /tmp/test-poetry-project && poetry init --no-interaction");
|
||||
|
||||
// Add a safe package
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-project && poetry add requests"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry add with specific version`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-version && cd /tmp/test-poetry-version");
|
||||
await shell.runCommand("cd /tmp/test-poetry-version && poetry init --no-interaction");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-version && poetry add requests==2.32.3"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`safe-chain blocks installation of malicious Python packages via poetry`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-malware && cd /tmp/test-poetry-malware");
|
||||
await shell.runCommand("cd /tmp/test-poetry-malware && poetry init --no-interaction");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-malware && poetry add safe-chain-pi-test"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("Blocked by Safe-chain"),
|
||||
`Expected malware to be blocked. Output was:\n${result.output}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.exitCode,
|
||||
1,
|
||||
`Expected exit code 1 for blocked malware, got ${result.exitCode}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry install installs dependencies from pyproject.toml`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-install && cd /tmp/test-poetry-install");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install && poetry add requests");
|
||||
|
||||
// Now remove the virtualenv and run install
|
||||
await shell.runCommand("cd /tmp/test-poetry-install && rm -rf .venv");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-install && poetry install"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry update updates dependencies`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-update && cd /tmp/test-poetry-update");
|
||||
await shell.runCommand("cd /tmp/test-poetry-update && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-update && poetry add requests");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-update && poetry update"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Updating"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry update with specific packages`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-update-specific && cd /tmp/test-poetry-update-specific");
|
||||
await shell.runCommand("cd /tmp/test-poetry-update-specific && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-update-specific && poetry add requests certifi");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-update-specific && poetry update requests"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Updating"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry sync synchronizes environment`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-sync && cd /tmp/test-poetry-sync");
|
||||
await shell.runCommand("cd /tmp/test-poetry-sync && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-sync && poetry add requests");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-sync && poetry sync"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry add with multiple packages`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-multi && cd /tmp/test-poetry-multi");
|
||||
await shell.runCommand("cd /tmp/test-poetry-multi && poetry init --no-interaction");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-multi && poetry add requests certifi"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry add with extras`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-extras && cd /tmp/test-poetry-extras");
|
||||
await shell.runCommand("cd /tmp/test-poetry-extras && poetry init --no-interaction");
|
||||
|
||||
// Use quotes to prevent shell expansion of square brackets
|
||||
const result = await shell.runCommand(
|
||||
'cd /tmp/test-poetry-extras && poetry add "requests[security]"'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry add with development group`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-dev && cd /tmp/test-poetry-dev");
|
||||
await shell.runCommand("cd /tmp/test-poetry-dev && poetry init --no-interaction");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-dev && poetry add --group dev pytest"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry install with extras`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-install-extras && cd /tmp/test-poetry-install-extras");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-extras && poetry init --no-interaction");
|
||||
await shell.runCommand('cd /tmp/test-poetry-install-extras && poetry add requests');
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-extras && rm -rf .venv");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
'cd /tmp/test-poetry-install-extras && poetry install'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry install with dependency groups`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-install-groups && cd /tmp/test-poetry-install-groups");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-groups && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-groups && poetry add requests");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-groups && rm -rf .venv");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-install-groups && poetry install"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry lock creates/updates lock file`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-lock && cd /tmp/test-poetry-lock");
|
||||
await shell.runCommand("cd /tmp/test-poetry-lock && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-lock && poetry add requests");
|
||||
await shell.runCommand("cd /tmp/test-poetry-lock && rm poetry.lock");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-lock && poetry lock"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Resolving") || result.output.includes("lock file"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry add with version constraint using @`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-constraint && cd /tmp/test-poetry-constraint");
|
||||
await shell.runCommand("cd /tmp/test-poetry-constraint && poetry init --no-interaction");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-constraint && poetry add requests@^2.32.0"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("no malware found.") || result.output.includes("Installing"),
|
||||
`Output did not include expected text. Output was:\n${result.output}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`poetry remove does not download packages`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-remove && cd /tmp/test-poetry-remove");
|
||||
await shell.runCommand("cd /tmp/test-poetry-remove && poetry init --no-interaction");
|
||||
await shell.runCommand("cd /tmp/test-poetry-remove && poetry add requests");
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-remove && poetry remove requests"
|
||||
);
|
||||
|
||||
// Remove should succeed - it doesn't download packages
|
||||
assert.strictEqual(
|
||||
result.status,
|
||||
0,
|
||||
`Expected exit code 0 for remove command, got ${result.status}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`blocks malware during poetry install`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
// Create a project with malware in dependencies
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-install-malware && cd /tmp/test-poetry-install-malware");
|
||||
await shell.runCommand("cd /tmp/test-poetry-install-malware && poetry init --no-interaction");
|
||||
|
||||
// Add safe-chain-pi-test to pyproject.toml using sed
|
||||
await shell.runCommand('cd /tmp/test-poetry-install-malware && echo "safe-chain-pi-test = \"*\"" >> pyproject.toml');
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-install-malware && poetry install 2>&1"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("Blocked by Safe-chain"),
|
||||
`Expected malware to be blocked during install. Output was:\n${result.output}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.status,
|
||||
1,
|
||||
`Expected exit code 1 for blocked malware during install, got ${result.status}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`blocks malware during poetry update`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-update-malware && cd /tmp/test-poetry-update-malware");
|
||||
await shell.runCommand("cd /tmp/test-poetry-update-malware && poetry init --no-interaction");
|
||||
|
||||
// Add safe-chain-pi-test to pyproject.toml using sed
|
||||
await shell.runCommand('cd /tmp/test-poetry-update-malware && echo "safe-chain-pi-test = \"*\"" >> pyproject.toml');
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-update-malware && poetry update 2>&1"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("Blocked by Safe-chain"),
|
||||
`Expected malware to be blocked during update. Output was:\n${result.output}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.status,
|
||||
1,
|
||||
`Expected exit code 1 for blocked malware during update, got ${result.status}`
|
||||
);
|
||||
});
|
||||
|
||||
it(`blocks malware during poetry sync`, async () => {
|
||||
const shell = await container.openShell("zsh");
|
||||
|
||||
await shell.runCommand("mkdir /tmp/test-poetry-sync-malware && cd /tmp/test-poetry-sync-malware");
|
||||
await shell.runCommand("cd /tmp/test-poetry-sync-malware && poetry init --no-interaction");
|
||||
|
||||
// Add safe-chain-pi-test to pyproject.toml using sed
|
||||
await shell.runCommand('cd /tmp/test-poetry-sync-malware && echo "safe-chain-pi-test = \"*\"" >> pyproject.toml');
|
||||
|
||||
const result = await shell.runCommand(
|
||||
"cd /tmp/test-poetry-sync-malware && poetry sync 2>&1"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
result.output.includes("Blocked by Safe-chain"),
|
||||
`Expected malware to be blocked during sync. Output was:\n${result.output}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.status,
|
||||
1,
|
||||
`Expected exit code 1 for blocked malware during sync, got ${result.status}`
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue