mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Fix concurrency bug leading to multiple fetches of the malware database
This commit is contained in:
parent
3e71398430
commit
2930894624
2 changed files with 51 additions and 55 deletions
|
|
@ -15,8 +15,12 @@ import { getEcoSystem, ECOSYSTEM_PY } from "../config/settings.js";
|
||||||
* @property {function(string, string): boolean} isMalware
|
* @property {function(string, string): boolean} isMalware
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @type {MalwareDatabase | null} */
|
// Caching the Promise (rather than the resolved database) prevents duplicate fetches. If we cached the resolved
|
||||||
let cachedMalwareDatabase = null;
|
// value, multiple callers could pass the null-check before the first fetch completes (because each `await` yields
|
||||||
|
// control back to the event loop, allowing other callers to run). Since the Promise assignment is synchronous, all
|
||||||
|
// concurrent callers see it immediately and share a single fetch.
|
||||||
|
/** @type {Promise<MalwareDatabase> | null} */
|
||||||
|
let cachedMalwareDatabasePromise = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize package name for comparison.
|
* Normalize package name for comparison.
|
||||||
|
|
@ -34,45 +38,41 @@ function normalizePackageName(name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openMalwareDatabase() {
|
export function openMalwareDatabase() {
|
||||||
if (cachedMalwareDatabase) {
|
if (!cachedMalwareDatabasePromise) {
|
||||||
return cachedMalwareDatabase;
|
cachedMalwareDatabasePromise = getMalwareDatabase().then((malwareDatabase) => {
|
||||||
}
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} version
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getPackageStatus(name, version) {
|
||||||
|
const normalizedName = normalizePackageName(name);
|
||||||
|
const packageData = malwareDatabase.find(
|
||||||
|
(pkg) => {
|
||||||
|
const normalizedPkgName = normalizePackageName(pkg.package_name);
|
||||||
|
return normalizedPkgName === normalizedName &&
|
||||||
|
(pkg.version === version || pkg.version === "*");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const malwareDatabase = await getMalwareDatabase();
|
if (!packageData) {
|
||||||
|
return MALWARE_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return packageData.reason;
|
||||||
* @param {string} name
|
|
||||||
* @param {string} version
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function getPackageStatus(name, version) {
|
|
||||||
const normalizedName = normalizePackageName(name);
|
|
||||||
const packageData = malwareDatabase.find(
|
|
||||||
(pkg) => {
|
|
||||||
const normalizedPkgName = normalizePackageName(pkg.package_name);
|
|
||||||
return normalizedPkgName === normalizedName &&
|
|
||||||
(pkg.version === version || pkg.version === "*");
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (!packageData) {
|
return {
|
||||||
return MALWARE_STATUS_OK;
|
getPackageStatus,
|
||||||
}
|
isMalware: (name, version) => {
|
||||||
|
const status = getPackageStatus(name, version);
|
||||||
return packageData.reason;
|
return isMalwareStatus(status);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return cachedMalwareDatabasePromise;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,26 @@ import { warnOnceAboutUnavailableDatabase } from "./newPackagesDatabaseWarnings.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Shared per-process cache to avoid rebuilding the same feed-backed database on each request.
|
// Shared per-process cache to avoid rebuilding the same feed-backed database on each request.
|
||||||
/** @type {NewPackagesDatabase | null} */
|
// Caching the Promise (rather than the resolved database) prevents duplicate fetches. If we cached the resolved
|
||||||
let cachedNewPackagesDatabase = null;
|
// value, multiple callers could pass the null-check before the first fetch completes (because each `await` yields
|
||||||
|
// control back to the event loop, allowing other callers to run). Since the Promise assignment is synchronous, all
|
||||||
|
// concurrent callers see it immediately and share a single fetch.
|
||||||
|
/** @type {Promise<NewPackagesDatabase> | null} */
|
||||||
|
let cachedNewPackagesDatabasePromise = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<NewPackagesDatabase>}
|
* @returns {Promise<NewPackagesDatabase>}
|
||||||
*/
|
*/
|
||||||
export async function openNewPackagesDatabase() {
|
export function openNewPackagesDatabase() {
|
||||||
if (cachedNewPackagesDatabase) {
|
if (!cachedNewPackagesDatabasePromise) {
|
||||||
return cachedNewPackagesDatabase;
|
cachedNewPackagesDatabasePromise = getNewPackagesList()
|
||||||
|
.then((newPackagesList) => buildNewPackagesDatabase(newPackagesList))
|
||||||
|
.catch((/** @type {any} */ error) => {
|
||||||
|
warnOnceAboutUnavailableDatabase(error);
|
||||||
|
return { isNewlyReleasedPackage: () => false };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return cachedNewPackagesDatabasePromise;
|
||||||
/** @type {import("../api/aikido.js").NewPackageEntry[]} */
|
|
||||||
let newPackagesList;
|
|
||||||
|
|
||||||
try {
|
|
||||||
newPackagesList = await getNewPackagesList();
|
|
||||||
} catch (/** @type {any} */ error) {
|
|
||||||
warnOnceAboutUnavailableDatabase(error);
|
|
||||||
cachedNewPackagesDatabase = { isNewlyReleasedPackage: () => false };
|
|
||||||
return cachedNewPackagesDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedNewPackagesDatabase = buildNewPackagesDatabase(newPackagesList);
|
|
||||||
return cachedNewPackagesDatabase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue