Remove --safe-chain-malware-action flag

This commit is contained in:
Sander Declerck 2025-10-27 11:51:19 +01:00
parent 95d9cefcc9
commit ab3319a310
No known key found for this signature in database
6 changed files with 8 additions and 188 deletions

View file

@ -1,5 +1,4 @@
const state = { const state = {
malwareAction: undefined,
loggingLevel: undefined, loggingLevel: undefined,
}; };
@ -7,7 +6,6 @@ const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
export function initializeCliArguments(args) { export function initializeCliArguments(args) {
// Reset state on each call // Reset state on each call
state.malwareAction = undefined;
state.loggingLevel = undefined; state.loggingLevel = undefined;
const safeChainArgs = []; const safeChainArgs = [];
@ -21,22 +19,11 @@ export function initializeCliArguments(args) {
} }
} }
setMalwareAction(safeChainArgs);
setLoggingLevel(safeChainArgs); setLoggingLevel(safeChainArgs);
return remainingArgs; return remainingArgs;
} }
function setMalwareAction(args) {
const safeChainMalwareActionArg = SAFE_CHAIN_ARG_PREFIX + "malware-action=";
const action = getLastArgEqualsValue(args, safeChainMalwareActionArg);
if (!action) {
return;
}
state.malwareAction = action.toLowerCase();
}
function getLastArgEqualsValue(args, prefix) { function getLastArgEqualsValue(args, prefix) {
for (var i = args.length - 1; i >= 0; i--) { for (var i = args.length - 1; i >= 0; i--) {
const arg = args[i]; const arg = args[i];
@ -48,10 +35,6 @@ function getLastArgEqualsValue(args, prefix) {
return undefined; return undefined;
} }
export function getMalwareAction() {
return state.malwareAction;
}
function setLoggingLevel(args) { function setLoggingLevel(args) {
const safeChainLoggingArg = SAFE_CHAIN_ARG_PREFIX + "logging="; const safeChainLoggingArg = SAFE_CHAIN_ARG_PREFIX + "logging=";

View file

@ -1,10 +1,6 @@
import { describe, it } from "node:test"; import { describe, it } from "node:test";
import assert from "node:assert"; import assert from "node:assert";
import { import { initializeCliArguments, getLoggingLevel } from "./cliArguments.js";
initializeCliArguments,
getMalwareAction,
getLoggingLevel,
} from "./cliArguments.js";
describe("initializeCliArguments", () => { describe("initializeCliArguments", () => {
it("should return all args when no safe-chain args are present", () => { it("should return all args when no safe-chain args are present", () => {
@ -61,55 +57,6 @@ describe("initializeCliArguments", () => {
assert.deepEqual(result, ["install", "my--safe-chain-package", "--save"]); assert.deepEqual(result, ["install", "my--safe-chain-package", "--save"]);
}); });
it("should not set malwareAction when no safe-chain arguments are passed", () => {
const args = ["install", "express", "--save"];
const result = initializeCliArguments(args);
assert.deepEqual(result, ["install", "express", "--save"]);
assert.strictEqual(getMalwareAction(), undefined);
});
it("should parse malware-action=block and set state", () => {
const args = ["--safe-chain-malware-action=block", "install", "package"];
const result = initializeCliArguments(args);
assert.deepEqual(result, ["install", "package"]);
assert.strictEqual(getMalwareAction(), "block");
});
it("should parse malware-action=prompt and set state", () => {
const args = ["--safe-chain-malware-action=prompt", "install", "package"];
const result = initializeCliArguments(args);
assert.deepEqual(result, ["install", "package"]);
assert.strictEqual(getMalwareAction(), "prompt");
});
it("should handle multiple malware-action args, using the last valid one", () => {
const args = [
"--safe-chain-malware-action=block",
"--safe-chain-malware-action=prompt",
"install",
];
const result = initializeCliArguments(args);
assert.deepEqual(result, ["install"]);
assert.strictEqual(getMalwareAction(), "prompt");
});
it("should handle malware-action with other safe-chain args", () => {
const args = [
"--safe-chain-debug",
"--safe-chain-malware-action=block",
"--safe-chain-verbose",
"install",
];
const result = initializeCliArguments(args);
assert.deepEqual(result, ["install"]);
assert.strictEqual(getMalwareAction(), "block");
});
it("should not set loggingLevel when no logging argument is passed", () => { it("should not set loggingLevel when no logging argument is passed", () => {
const args = ["install", "express", "--save"]; const args = ["install", "express", "--save"];
initializeCliArguments(args); initializeCliArguments(args);
@ -170,6 +117,5 @@ describe("initializeCliArguments", () => {
assert.deepEqual(result, ["install"]); assert.deepEqual(result, ["install"]);
assert.strictEqual(getLoggingLevel(), "silent"); assert.strictEqual(getLoggingLevel(), "silent");
assert.strictEqual(getMalwareAction(), "block");
}); });
}); });

View file

@ -1,15 +1,5 @@
import * as cliArguments from "./cliArguments.js"; import * as cliArguments from "./cliArguments.js";
export function getMalwareAction() {
const action = cliArguments.getMalwareAction();
if (action === MALWARE_ACTION_PROMPT) {
return MALWARE_ACTION_PROMPT;
}
return MALWARE_ACTION_BLOCK;
}
export function getLoggingLevel() { export function getLoggingLevel() {
const level = cliArguments.getLoggingLevel(); const level = cliArguments.getLoggingLevel();
@ -20,8 +10,5 @@ export function getLoggingLevel() {
return LOGGING_NORMAL; return LOGGING_NORMAL;
} }
export const MALWARE_ACTION_BLOCK = "block";
export const MALWARE_ACTION_PROMPT = "prompt";
export const LOGGING_SILENT = "silent"; export const LOGGING_SILENT = "silent";
export const LOGGING_NORMAL = "normal"; export const LOGGING_NORMAL = "normal";

View file

@ -1,7 +1,6 @@
// oxlint-disable no-console // oxlint-disable no-console
import chalk from "chalk"; import chalk from "chalk";
import ora from "ora"; import ora from "ora";
import { createInterface } from "readline";
import { isCi } from "./environment.js"; import { isCi } from "./environment.js";
import { getLoggingLevel, LOGGING_SILENT } from "../config/settings.js"; import { getLoggingLevel, LOGGING_SILENT } from "../config/settings.js";
@ -87,34 +86,6 @@ function startProcess(message) {
} }
} }
async function confirm(config) {
if (isCi() || isSilentMode()) {
return Promise.resolve(config.default);
}
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
const defaultText = config.default ? " (Y/n)" : " (y/N)";
rl.question(`${config.message}${defaultText} `, (answer) => {
rl.close();
const normalizedAnswer = answer.trim().toLowerCase();
if (normalizedAnswer === "y" || normalizedAnswer === "yes") {
resolve(true);
} else if (normalizedAnswer === "n" || normalizedAnswer === "no") {
resolve(false);
} else {
resolve(config.default);
}
});
});
}
export const ui = { export const ui = {
writeInformation, writeInformation,
writeWarning, writeWarning,
@ -122,5 +93,4 @@ export const ui = {
writeExitWithoutInstallingMaliciousPackages, writeExitWithoutInstallingMaliciousPackages,
emptyLine, emptyLine,
startProcess, startProcess,
confirm,
}; };

View file

@ -4,7 +4,6 @@ import { setTimeout } from "timers/promises";
import chalk from "chalk"; import chalk from "chalk";
import { getPackageManager } from "../packagemanager/currentPackageManager.js"; import { getPackageManager } from "../packagemanager/currentPackageManager.js";
import { ui } from "../environment/userInteraction.js"; import { ui } from "../environment/userInteraction.js";
import { getMalwareAction, MALWARE_ACTION_PROMPT } from "../config/settings.js";
export function shouldScanCommand(args) { export function shouldScanCommand(args) {
if (!args || args.length === 0) { if (!args || args.length === 0) {
@ -65,7 +64,8 @@ export async function scanCommand(args) {
return 0; return 0;
} else { } else {
printMaliciousChanges(audit.disallowedChanges, spinner); printMaliciousChanges(audit.disallowedChanges, spinner);
return await onMalwareFound(); onMalwareFound();
return 1;
} }
} }
@ -77,23 +77,8 @@ function printMaliciousChanges(changes, spinner) {
} }
} }
async function onMalwareFound() { function onMalwareFound() {
ui.emptyLine(); ui.emptyLine();
if (getMalwareAction() === MALWARE_ACTION_PROMPT) {
const continueInstall = await ui.confirm({
message:
"Malicious packages were found. Do you want to continue with the installation?",
default: false,
});
if (continueInstall) {
ui.writeWarning("Continuing with the installation despite the risks...");
return 0;
}
}
ui.writeExitWithoutInstallingMaliciousPackages(); ui.writeExitWithoutInstallingMaliciousPackages();
ui.emptyLine(); ui.emptyLine();
return 1;
} }

View file

@ -1,10 +1,6 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { beforeEach, describe, it, mock } from "node:test"; import { describe, it, mock } from "node:test";
import { setTimeout } from "node:timers/promises"; import { setTimeout } from "node:timers/promises";
import {
MALWARE_ACTION_PROMPT,
MALWARE_ACTION_BLOCK,
} from "../config/settings.js";
describe("scanCommand", async () => { describe("scanCommand", async () => {
const getScanTimeoutMock = mock.fn(() => 1000); const getScanTimeoutMock = mock.fn(() => 1000);
@ -15,8 +11,6 @@ describe("scanCommand", async () => {
fail: () => {}, fail: () => {},
stop: () => {}, stop: () => {},
})); }));
const mockConfirm = mock.fn(() => true);
let malwareAction = MALWARE_ACTION_PROMPT;
// import { getPackageManager } from "../packagemanager/currentPackageManager.js"; // import { getPackageManager } from "../packagemanager/currentPackageManager.js";
mock.module("../packagemanager/currentPackageManager.js", { mock.module("../packagemanager/currentPackageManager.js", {
@ -48,19 +42,10 @@ describe("scanCommand", async () => {
writeWarning: () => {}, writeWarning: () => {},
writeExitWithoutInstallingMaliciousPackages: () => {}, writeExitWithoutInstallingMaliciousPackages: () => {},
emptyLine: () => {}, emptyLine: () => {},
confirm: mockConfirm,
}, },
}, },
}); });
mock.module("../config/settings.js", {
namedExports: {
getMalwareAction: () => malwareAction,
MALWARE_ACTION_PROMPT,
MALWARE_ACTION_BLOCK,
},
});
// import { auditChanges, MAX_LENGTH_EXCEEDED } from "./audit/index.js"; // import { auditChanges, MAX_LENGTH_EXCEEDED } from "./audit/index.js";
mock.module("./audit/index.js", { mock.module("./audit/index.js", {
namedExports: { namedExports: {
@ -89,11 +74,6 @@ describe("scanCommand", async () => {
const { scanCommand } = await import("./index.js"); const { scanCommand } = await import("./index.js");
beforeEach(() => {
// Reset malware action back to prompt mode for other tests
malwareAction = MALWARE_ACTION_PROMPT;
});
it("should succeed when there are no changes", async () => { it("should succeed when there are no changes", async () => {
let progressWasStopped = false; let progressWasStopped = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
@ -151,7 +131,7 @@ describe("scanCommand", async () => {
assert.equal(failureMessageWasSet, true); assert.equal(failureMessageWasSet, true);
}); });
it("should fail and prompt the user when malicious changes are detected", async () => { it("should fail and return 1 malicious changes are detected", async () => {
let failureMessageWasSet = false; let failureMessageWasSet = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
@ -164,16 +144,11 @@ describe("scanCommand", async () => {
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
]); ]);
let userWasPrompted = false;
mockConfirm.mock.mockImplementationOnce(() => {
userWasPrompted = true;
return true; // Simulate user accepting the risk, otherwise the process would exit
});
await scanCommand(["install", "malicious"]); const result = await scanCommand(["install", "malicious"]);
assert.equal(failureMessageWasSet, true); assert.equal(failureMessageWasSet, true);
assert.equal(userWasPrompted, true); assert.equal(result, 1);
}); });
it("should not report a timeout when the user takes a long time to respond (it should not affect the timeout)", async () => { it("should not report a timeout when the user takes a long time to respond (it should not affect the timeout)", async () => {
@ -190,10 +165,6 @@ describe("scanCommand", async () => {
mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => {
return [{ name: "malicious", version: "4.17.21" }]; return [{ name: "malicious", version: "4.17.21" }];
}); });
mockConfirm.mock.mockImplementationOnce(async () => {
await setTimeout(200);
return true; // Simulate user accepting the risk, otherwise the process would exit
});
await scanCommand(["install", "malicious"]); await scanCommand(["install", "malicious"]);
@ -204,12 +175,6 @@ describe("scanCommand", async () => {
}); });
it("should exit immediately when malicious changes are detected in block mode", async () => { it("should exit immediately when malicious changes are detected in block mode", async () => {
// Set malware action to block mode for this test
malwareAction = MALWARE_ACTION_BLOCK;
// Reset mock call count
mockConfirm.mock.resetCalls();
let failureMessageWasSet = false; let failureMessageWasSet = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
@ -229,19 +194,9 @@ describe("scanCommand", async () => {
assert.equal(failureMessageWasSet, true); assert.equal(failureMessageWasSet, true);
assert.equal(result, 1); assert.equal(result, 1);
// Confirm should not have been called in block mode
assert.equal(mockConfirm.mock.callCount(), 0);
}); });
it("should exit immediately when malicious changes are detected in block mode without prompting", async () => { it("should exit immediately when malicious changes are detected in block mode without prompting", async () => {
// Set malware action to block mode for this test
malwareAction = MALWARE_ACTION_BLOCK;
// Reset mock call count
mockConfirm.mock.resetCalls();
let userWasPrompted = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
succeed: () => {}, succeed: () => {},
@ -253,14 +208,8 @@ describe("scanCommand", async () => {
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
]); ]);
mockConfirm.mock.mockImplementationOnce(() => {
userWasPrompted = true;
return false;
});
const result = await scanCommand(["install", "malicious"]); const result = await scanCommand(["install", "malicious"]);
assert.equal(result, 1); assert.equal(result, 1);
assert.equal(userWasPrompted, false);
}); });
}); });