Mirror malware list in e2e tests to mock malware in a harmless way

This commit is contained in:
Sander Declerck 2026-04-28 14:47:49 +02:00
parent 222216e22a
commit ebebe6d6c1
No known key found for this signature in database
9 changed files with 114 additions and 24 deletions

View file

@ -58,12 +58,21 @@ export class DockerTestContainer {
`docker run -d --name ${this.containerName} ${imageName} sleep infinity`,
{ stdio: "ignore" }
);
await this.startMalwareMirror();
this.isRunning = true;
} catch (error) {
throw new Error(`Failed to start container: ${error.message}`);
}
}
async startMalwareMirror() {
const shell = await this.openShell("zsh");
await shell.runCommand("node /utils/malwarelistmirror.mjs &");
await shell.runCommand("until curl -sf http://127.0.0.1:5555/ready; do sleep 0.2; done");
}
dockerExec(command, daemon = false) {
if (!this.isRunning) {
throw new Error("Container is not running");
@ -125,7 +134,7 @@ export class DockerTestContainer {
const timeout = setTimeout(() => {
// Fallback in case the command doesn't finish in a reasonable time
// oxlint-disable-next-line no-console - having this log in CI helps diagnose issues
console.log("Command timeout reached");
console.log(`Command timeout reached for "${command}"`);
resolve({ allData, output: parseShellOutput(allData), command });
ptyProcess.removeListener("data", handleInput);
}, 15000);

View file

@ -84,3 +84,5 @@ RUN npm install -g /pkgs/*.tgz
WORKDIR /testapp
RUN npm init -y
COPY test/e2e/utils/malwarelistmirror.mjs /utils/malwarelistmirror.mjs
ENV SAFE_CHAIN_MALWARE_LIST_BASE_URL=http://127.0.0.1:5555

View file

@ -128,7 +128,7 @@ describe("E2E: pip coverage", () => {
it(`safe-chain blocks installation of malicious Python packages`, async () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"pip3 install --break-system-packages safe-chain-pi-test"
"pip3 install --break-system-packages numpy==2.4.4 --safe-chain-logging=verbose"
);
assert.ok(
@ -136,7 +136,7 @@ describe("E2E: pip coverage", () => {
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
result.output.includes("safe_chain_pi_test@0.0.1"),
result.output.includes("numpy@2.4.4"),
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
@ -146,7 +146,7 @@ describe("E2E: pip coverage", () => {
const listResult = await shell.runCommand("pip3 list");
assert.ok(
!listResult.output.includes("safe-chain-pi-test"),
!listResult.output.includes("numpy"),
`Malicious package was installed despite safe-chain protection. Output of 'pip3 list' was:\n${listResult.output}`
);
});

View file

@ -41,7 +41,7 @@ describe("E2E: pipx coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"pipx install safe-chain-pi-test"
"pipx install numpy==2.4.4"
);
assert.ok(
@ -86,7 +86,7 @@ describe("E2E: pipx coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"pipx run safe-chain-pi-test --version"
"pipx run numpy==2.4.4 --version"
);
assert.ok(
@ -122,7 +122,7 @@ describe("E2E: pipx coverage", () => {
await shell.runCommand("pipx install ruff");
const result = await shell.runCommand(
"pipx runpip ruff install safe-chain-pi-test"
"pipx runpip ruff install numpy==2.4.4"
);
assert.ok(
@ -185,7 +185,7 @@ describe("E2E: pipx coverage", () => {
await shell.runCommand("pipx install ruff --safe-chain-logging=verbose");
const result = await shell.runCommand(
"pipx inject ruff safe-chain-pi-test --safe-chain-logging=verbose"
"pipx inject ruff numpy==2.4.4 --safe-chain-logging=verbose"
);
assert.ok(

View file

@ -70,7 +70,7 @@ describe("E2E: poetry coverage", () => {
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"
"cd /tmp/test-poetry-malware && poetry add numpy==2.4.4"
);
assert.ok(
@ -300,7 +300,7 @@ describe("E2E: poetry coverage", () => {
// Add malware package - this will create lock file and attempt download
const result = await shell.runCommand(
"cd /tmp/test-poetry-install-malware && poetry add safe-chain-pi-test 2>&1"
"cd /tmp/test-poetry-install-malware && poetry add numpy==2.4.4 2>&1"
);
assert.ok(
@ -324,7 +324,7 @@ describe("E2E: poetry coverage", () => {
// Now try to add malware via add command
const result = await shell.runCommand(
"cd /tmp/test-poetry-update-add && poetry add safe-chain-pi-test 2>&1"
"cd /tmp/test-poetry-update-add && poetry add numpy==2.4.4 2>&1"
);
assert.ok(
@ -345,7 +345,7 @@ describe("E2E: poetry coverage", () => {
// Try to add malware directly - this is the primary vector
const result = await shell.runCommand(
"cd /tmp/test-poetry-req-malware && poetry add safe-chain-pi-test requests 2>&1"
"cd /tmp/test-poetry-req-malware && poetry add numpy==2.4.4 requests 2>&1"
);
assert.ok(

View file

@ -97,7 +97,7 @@ describe("E2E: safe-chain CLI python/pip support", () => {
await shell.runCommand("pip3 cache purge");
const result = await shell.runCommand(
"safe-chain pip3 install --break-system-packages safe-chain-pi-test"
"safe-chain pip3 install --break-system-packages numpy==2.4.4"
);
assert.ok(

View file

@ -0,0 +1,79 @@
// Test-only mirror of the malware list. Injects known-safe packages as malicious
// to simulate blocking behavior in e2e tests without affecting real data.
import * as http from "node:http";
const lists = await downloadLists();
const server = http.createServer(handleRequest);
server.listen(5555, "127.0.0.1");
console.log("listening on http://127.0.0.1:5555");
function handleRequest(req, res) {
if (req.method !== "GET" || !req.url) {
res.writeHead(404);
res.end();
return;
}
if (req.url.startsWith("/ready")) {
res.writeHead(200);
res.end();
return;
}
for (const list of lists) {
if (req.url.startsWith(list.path)) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(list.data));
return;
}
}
res.writeHead(404);
res.end();
}
async function downloadLists() {
const lists = [
{
"path": "/malware_predictions.json",
"patchFunc": (data) => data,
},
{
"path": "/malware_pypi.json",
"patchFunc": patchPypi,
},
{
"path": "/releases/npm.json",
"patchFunc": (data) => data,
},
{
"path": "/releases/pypi.json",
"patchFunc": (data) => data,
},
]
for (const list of lists) {
list.data = list.patchFunc(await downloadList(list.path));
}
return lists;
}
async function downloadList(path) {
const baseUrl = "https://malware-list.aikido.dev";
const url = `${baseUrl}${path}`;
const response = await fetch(url);
return await response.json();
}
function patchPypi(data) {
data.push({
"package_name": "numpy",
"version": "2.4.4",
"reason": "MALWARE"
});
return data;
}

View file

@ -126,7 +126,7 @@ describe("E2E: uv coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"uv pip install --system --break-system-packages safe-chain-pi-test"
"uv pip install --system --break-system-packages numpy==2.4.4"
);
assert.ok(
@ -134,7 +134,7 @@ describe("E2E: uv coverage", () => {
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
result.output.includes("safe_chain_pi_test@0.0.1"),
result.output.includes("numpy@2.4.4"),
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
@ -144,7 +144,7 @@ describe("E2E: uv coverage", () => {
const listResult = await shell.runCommand("uv pip list --system");
assert.ok(
!listResult.output.includes("safe-chain-pi-test"),
!listResult.output.includes("numpy"),
`Malicious package was installed despite safe-chain protection. Output of 'uv pip list' was:\n${listResult.output}`
);
});
@ -413,7 +413,7 @@ describe("E2E: uv coverage", () => {
await shell.runCommand("uv init test-project-malware");
const result = await shell.runCommand(
"cd test-project-malware && uv add safe-chain-pi-test"
"cd test-project-malware && uv add numpy==2.4.4"
);
assert.ok(
@ -421,7 +421,7 @@ describe("E2E: uv coverage", () => {
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
result.output.includes("safe_chain_pi_test@0.0.1"),
result.output.includes("numpy@2.4.4"),
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
@ -445,14 +445,14 @@ describe("E2E: uv coverage", () => {
it(`safe-chain blocks malicious packages via uv tool install`, async () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand("uv tool install safe-chain-pi-test");
const result = await shell.runCommand("uv tool install numpy==2.4.4");
assert.ok(
result.output.includes("blocked 1 malicious package downloads:"),
`Output did not include expected text. Output was:\n${result.output}`
);
assert.ok(
result.output.includes("safe_chain_pi_test@0.0.1"),
result.output.includes("numpy@2.4.4"),
`Output did not include expected text. Output was:\n${result.output}`
);
});
@ -482,7 +482,7 @@ describe("E2E: uv coverage", () => {
await shell.runCommand("echo 'print(\"test\")' > test_script2.py");
const result = await shell.runCommand(
"uv run --with safe-chain-pi-test test_script2.py"
"uv run --with numpy==2.4.4 test_script2.py"
);
assert.ok(

View file

@ -44,7 +44,7 @@ describe("E2E: uvx coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"uvx safe-chain-pi-test"
"uvx numpy==2.4.4"
);
assert.ok(
@ -74,7 +74,7 @@ describe("E2E: uvx coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"uvx --from safe-chain-pi-test some-command"
"uvx --from numpy==2.4.4 some-command"
);
assert.ok(
@ -117,7 +117,7 @@ describe("E2E: uvx coverage", () => {
const shell = await container.openShell("zsh");
const result = await shell.runCommand(
"uvx --with safe-chain-pi-test ruff --version"
"uvx --with numpy==2.4.4 ruff --version"
);
assert.ok(