mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Remove --safe-chain-malware-action flag
This commit is contained in:
parent
95d9cefcc9
commit
ab3319a310
6 changed files with 8 additions and 188 deletions
|
|
@ -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=";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue