Type check safe-chain package

This commit is contained in:
Hans Ott 2025-11-01 13:06:06 +01:00
parent d5dc801c00
commit c88b1a624f
60 changed files with 1179 additions and 33 deletions

View file

@ -3,6 +3,25 @@ import {
openMalwareDatabase,
} from "../malwareDatabase.js";
/**
* @typedef PackageChange
* @property {string} name
* @property {string} version
* @property {string} type
*/
/**
* @typedef AuditResult
* @property {PackageChange[]} allowedChanges
* @property {(PackageChange & {reason: string})[]} disallowedChanges
* @property {boolean} isAllowed
*/
/**
* @param {PackageChange[]} changes
*
* @returns {Promise<AuditResult>}
*/
export async function auditChanges(changes) {
const allowedChanges = [];
const disallowedChanges = [];
@ -34,6 +53,10 @@ export async function auditChanges(changes) {
return auditResults;
}
/**
* @param {{name: string, version: string, type: string}[]} changes
* @returns {Promise<{name: string, version: string, status: string}[]>}
*/
async function getPackagesWithMalware(changes) {
if (changes.length === 0) {
return [];

View file

@ -5,6 +5,11 @@ import chalk from "chalk";
import { getPackageManager } from "../packagemanager/currentPackageManager.js";
import { ui } from "../environment/userInteraction.js";
/**
* @param {string[]} args
*
* @returns {boolean}
*/
export function shouldScanCommand(args) {
if (!args || args.length === 0) {
return false;
@ -13,6 +18,11 @@ export function shouldScanCommand(args) {
return getPackageManager().isSupportedCommand(args);
}
/**
* @param {string[]} args
*
* @returns {Promise<number | never[]>}
*/
export async function scanCommand(args) {
if (!shouldScanCommand(args)) {
return [];
@ -23,6 +33,7 @@ export async function scanCommand(args) {
const spinner = ui.startProcess(
"Safe-chain: Scanning for malicious packages..."
);
/** @type {import("./audit/index.js").AuditResult | undefined} */
let audit;
await Promise.race([
@ -44,7 +55,7 @@ export async function scanCommand(args) {
}
audit = await auditChanges(changes);
} catch (error) {
} catch (/** @type any */ error) {
spinner.fail(`Safe-chain: Error while scanning.`);
throw error;
}
@ -69,6 +80,12 @@ export async function scanCommand(args) {
}
}
/**
* @param {import("./audit/index.js").PackageChange[]} changes
* @param spinner {import("../environment/userInteraction.js").Spinner}
*
* @return {void}
*/
function printMaliciousChanges(changes, spinner) {
spinner.fail("Safe-chain: " + chalk.bold("Malicious changes detected:"));

View file

@ -8,6 +8,13 @@ import {
} from "../config/configFile.js";
import { ui } from "../environment/userInteraction.js";
/**
* @typedef MalwareDatabase
* @property {function(string, string): string} getPackageStatus
* @property {function(string, string): boolean} isMalware
*/
/** @type {MalwareDatabase | null} */
let cachedMalwareDatabase = null;
export async function openMalwareDatabase() {
@ -17,6 +24,11 @@ export async function openMalwareDatabase() {
const malwareDatabase = await getMalwareDatabase();
/**
* @param {string} name
* @param {string} version
* @returns {string}
*/
function getPackageStatus(name, version) {
const packageData = malwareDatabase.find(
(pkg) =>
@ -31,7 +43,7 @@ export async function openMalwareDatabase() {
return packageData.reason;
}
// This implicitely caches the malware database
// This implicitly caches the malware database
// that's closed over by the getPackageStatus function
cachedMalwareDatabase = {
getPackageStatus,
@ -43,6 +55,9 @@ export async function openMalwareDatabase() {
return cachedMalwareDatabase;
}
/**
* @returns {Promise<import("../api/aikido.js").MalwarePackage[]>}
*/
async function getMalwareDatabase() {
const { malwareDatabase: cachedDatabase, version: cachedVersion } =
readDatabaseFromLocalCache();
@ -56,10 +71,11 @@ async function getMalwareDatabase() {
}
const { malwareDatabase, version } = await fetchMalwareDatabase();
// @ts-expect-error version can be undefined
writeDatabaseToLocalCache(malwareDatabase, version);
return malwareDatabase;
} catch (error) {
} catch (/** @type any */ error) {
if (cachedDatabase) {
ui.writeWarning(
"Failed to fetch the latest malware database. Using cached version."
@ -70,6 +86,11 @@ async function getMalwareDatabase() {
}
}
/**
* @param {string} status
*
* @returns {boolean}
*/
function isMalwareStatus(status) {
let malwareStatus = status.toUpperCase();
return malwareStatus === MALWARE_STATUS_MALWARE;