mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 04:00:49 +00:00
92 lines
2.3 KiB
JavaScript
92 lines
2.3 KiB
JavaScript
import { auditChanges } from "./audit/index.js";
|
|
import { getScanTimeout } from "../config/configFile.js";
|
|
import { setTimeout } from "timers/promises";
|
|
import chalk from "chalk";
|
|
import { getPackageManager } from "../packagemanager/currentPackageManager.js";
|
|
import { ui } from "../environment/userInteraction.js";
|
|
|
|
export function shouldScanCommand(args) {
|
|
if (!args || args.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
return getPackageManager().isSupportedCommand(args);
|
|
}
|
|
|
|
export async function scanCommand(args) {
|
|
if (!shouldScanCommand(args)) {
|
|
return [];
|
|
}
|
|
|
|
let timedOut = false;
|
|
|
|
const spinner = ui.startProcess("Scanning for malicious packages...");
|
|
let audit;
|
|
|
|
await Promise.race([
|
|
(async () => {
|
|
try {
|
|
const packageManager = getPackageManager();
|
|
const changes = await packageManager.getDependencyUpdatesForCommand(
|
|
args
|
|
);
|
|
|
|
if (timedOut) {
|
|
return;
|
|
}
|
|
|
|
if (changes.length > 0) {
|
|
spinner.setText(`Scanning ${changes.length} package(s)...`);
|
|
}
|
|
|
|
audit = await auditChanges(changes);
|
|
} catch (error) {
|
|
spinner.fail(`Error while scanning: ${error.message}`);
|
|
throw error;
|
|
}
|
|
})(),
|
|
setTimeout(getScanTimeout()).then(() => {
|
|
timedOut = true;
|
|
}),
|
|
]);
|
|
|
|
if (timedOut) {
|
|
spinner.fail("Timeout exceeded while scanning.");
|
|
throw new Error("Timeout exceeded while scanning npm install command.");
|
|
}
|
|
|
|
if (!audit || audit.isAllowed) {
|
|
spinner.succeed("No malicious packages detected.");
|
|
} else {
|
|
printMaliciousChanges(audit.disallowedChanges, spinner);
|
|
await acceptRiskOrExit(
|
|
"Do you want to continue with the installation despite the risks?",
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
function printMaliciousChanges(changes, spinner) {
|
|
spinner.fail(chalk.bold("Malicious changes detected:"));
|
|
|
|
for (const change of changes) {
|
|
ui.writeInformation(` - ${change.name}@${change.version}`);
|
|
}
|
|
}
|
|
|
|
async function acceptRiskOrExit(message, defaultValue) {
|
|
ui.emptyLine();
|
|
const continueInstall = await ui.confirm({
|
|
message: message,
|
|
default: defaultValue,
|
|
});
|
|
|
|
if (continueInstall) {
|
|
ui.writeInformation("Continuing with the installation...");
|
|
return;
|
|
}
|
|
|
|
ui.writeInformation("Exiting without installing packages.");
|
|
ui.emptyLine();
|
|
process.exit(1);
|
|
}
|