mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
When you write @typedef {Object} ScanResult, you’re telling both JSDoc and TypeScript’s parser that this typedef represents an object type, not just an abstract name. This is important because it makes tools like IDEs, linters, and TypeScript’s JSDoc inference more reliable. It avoids ambiguity, especially in cases where the typedef might later be confused with something like a primitive, union, or function type. The official TypeScript documentation and the JSDoc spec both show this form as the canonical one for object shapes.
102 lines
2.6 KiB
JavaScript
102 lines
2.6 KiB
JavaScript
import {
|
|
fetchMalwareDatabase,
|
|
fetchMalwareDatabaseVersion,
|
|
} from "../api/aikido.js";
|
|
import {
|
|
readDatabaseFromLocalCache,
|
|
writeDatabaseToLocalCache,
|
|
} from "../config/configFile.js";
|
|
import { ui } from "../environment/userInteraction.js";
|
|
|
|
/**
|
|
* @typedef {Object} MalwareDatabase
|
|
* @property {function(string, string): string} getPackageStatus
|
|
* @property {function(string, string): boolean} isMalware
|
|
*/
|
|
|
|
/** @type {MalwareDatabase | null} */
|
|
let cachedMalwareDatabase = null;
|
|
|
|
export async function openMalwareDatabase() {
|
|
if (cachedMalwareDatabase) {
|
|
return cachedMalwareDatabase;
|
|
}
|
|
|
|
const malwareDatabase = await getMalwareDatabase();
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {string} version
|
|
* @returns {string}
|
|
*/
|
|
function getPackageStatus(name, version) {
|
|
const packageData = malwareDatabase.find(
|
|
(pkg) =>
|
|
pkg.package_name === name &&
|
|
(pkg.version === version || pkg.version === "*")
|
|
);
|
|
|
|
if (!packageData) {
|
|
return MALWARE_STATUS_OK;
|
|
}
|
|
|
|
return packageData.reason;
|
|
}
|
|
|
|
// This implicitly caches the malware database
|
|
// that's closed over by the getPackageStatus function
|
|
cachedMalwareDatabase = {
|
|
getPackageStatus,
|
|
isMalware: (name, version) => {
|
|
const status = getPackageStatus(name, version);
|
|
return isMalwareStatus(status);
|
|
},
|
|
};
|
|
return cachedMalwareDatabase;
|
|
}
|
|
|
|
/**
|
|
* @returns {Promise<import("../api/aikido.js").MalwarePackage[]>}
|
|
*/
|
|
async function getMalwareDatabase() {
|
|
const { malwareDatabase: cachedDatabase, version: cachedVersion } =
|
|
readDatabaseFromLocalCache();
|
|
|
|
try {
|
|
if (cachedDatabase) {
|
|
const currentVersion = await fetchMalwareDatabaseVersion();
|
|
if (cachedVersion === currentVersion) {
|
|
return cachedDatabase;
|
|
}
|
|
}
|
|
|
|
const { malwareDatabase, version } = await fetchMalwareDatabase();
|
|
// @ts-expect-error version can be undefined
|
|
writeDatabaseToLocalCache(malwareDatabase, version);
|
|
|
|
return malwareDatabase;
|
|
} catch (/** @type any */ error) {
|
|
if (cachedDatabase) {
|
|
ui.writeWarning(
|
|
"Failed to fetch the latest malware database. Using cached version."
|
|
);
|
|
return cachedDatabase;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} status
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
function isMalwareStatus(status) {
|
|
let malwareStatus = status.toUpperCase();
|
|
return malwareStatus === MALWARE_STATUS_MALWARE;
|
|
}
|
|
|
|
export const MALWARE_STATUS_OK = "OK";
|
|
export const MALWARE_STATUS_MALWARE = "MALWARE";
|
|
export const MALWARE_STATUS_TELEMETRY = "TELEMETRY";
|
|
export const MALWARE_STATUS_PROTESTWARE = "PROTESTWARE";
|