From fcc873e13722db5cbf44d5ceb30a142924b3520c Mon Sep 17 00:00:00 2001 From: VitoAlbano Date: Fri, 29 May 2026 14:52:09 +0100 Subject: [PATCH] [AAE-46514] - Fixing other improvements and comments --- .vscode/extensions.json | 6 +- scripts/check-security.mjs | 33 +++++- scripts/preinstall-check.mjs | 208 ++++++++++++++++++----------------- 3 files changed, 137 insertions(+), 110 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 90edca035c..cbd8703467 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,5 @@ { - "recommendations": [ - "meterian.meterian-heidi" - ] + "recommendations": [ + "meterian.meterian-heidi" + ] } diff --git a/scripts/check-security.mjs b/scripts/check-security.mjs index 7adaef0cfe..ac78c0bd5a 100644 --- a/scripts/check-security.mjs +++ b/scripts/check-security.mjs @@ -154,7 +154,8 @@ async function fetchFromOSV(projectDependencies) { package: { name: dep.name, ecosystem: 'npm' }, version: dep.version })) - }) + }), + signal: AbortSignal.timeout(30000) }); if (response.ok) { @@ -300,7 +301,8 @@ async function fetchFromGitHubAdvisory() { headers: { 'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' - } + }, + signal: AbortSignal.timeout(15000) } ); @@ -601,7 +603,9 @@ async function main() { violations.push(...checkLockfileDependencies(lockfile.packages, blockedPackages)); } - // Check with Meterian CLI for additional vulnerability coverage + // Check with Meterian CLI for additional vulnerability coverage (informational by default) + // Set ADF_METERIAN_BLOCK=1 to make Meterian findings block installation + const meterianFindings = []; if (lockfile?.packages) { console.log(''); const deps = Object.entries(lockfile.packages) @@ -614,7 +618,7 @@ async function main() { const meterianResult = await checkWithMeterian(deps); for (const vuln of meterianResult.vulnerable || []) { - violations.push({ + meterianFindings.push({ package: vuln.name, version: vuln.version, reason: `${vuln.severity}: ${vuln.id}${vuln.safeVersions?.length ? ` (safe: ${vuln.safeVersions[0]})` : ''}`, @@ -623,6 +627,18 @@ async function main() { } } + // Meterian findings are informational unless ADF_METERIAN_BLOCK is set + const blockOnMeterian = process.env.ADF_METERIAN_BLOCK === '1' || process.env.ADF_METERIAN_BLOCK === 'true'; + if (blockOnMeterian) { + violations.push(...meterianFindings); + } else if (meterianFindings.length > 0) { + console.log('\nāš ļø Meterian found vulnerabilities (informational, not blocking):'); + for (const v of meterianFindings) { + console.log(` ${v.package}@${v.version} - ${v.reason}`); + } + console.log(' Set ADF_METERIAN_BLOCK=1 to block on these findings.\n'); + } + // Deduplicate const uniqueViolations = violations.filter((v, i, arr) => arr.findIndex(x => x.package === v.package && x.version === v.version) === i @@ -651,9 +667,12 @@ async function main() { console.error(' • Exfiltrate sensitive data\n'); // Delete node_modules to prevent using compromised packages + // Set ADF_SECURITY_KEEP_NODE_MODULES=1 to skip deletion (e.g., in CI for caching) + const skipDeletion = process.env.ADF_SECURITY_KEEP_NODE_MODULES === '1' || process.env.ADF_SECURITY_KEEP_NODE_MODULES === 'true'; const nodeModulesPath = join(ROOT_DIR, 'node_modules'); - if (existsSync(nodeModulesPath)) { - console.error('šŸ—‘ļø Removing node_modules to prevent use of compromised packages...\n'); + if (existsSync(nodeModulesPath) && !skipDeletion) { + console.error('šŸ—‘ļø Removing node_modules to prevent use of compromised packages...'); + console.error(' (Set ADF_SECURITY_KEEP_NODE_MODULES=1 to skip deletion)\n'); try { rmSync(nodeModulesPath, { recursive: true, force: true }); console.error('āœ… node_modules deleted successfully.\n'); @@ -661,6 +680,8 @@ async function main() { console.error(`āš ļø Could not delete node_modules: ${e.message}`); console.error(' Please delete it manually before proceeding.\n'); } + } else if (skipDeletion) { + console.error('āš ļø Skipping node_modules deletion (ADF_SECURITY_KEEP_NODE_MODULES=1)\n'); } console.error('šŸ“‹ REQUIRED ACTIONS:'); diff --git a/scripts/preinstall-check.mjs b/scripts/preinstall-check.mjs index 4f90a7853c..78828029d3 100644 --- a/scripts/preinstall-check.mjs +++ b/scripts/preinstall-check.mjs @@ -58,56 +58,83 @@ function readCache() { try { const data = JSON.parse(readFileSync(cachePath, 'utf8')); if (Date.now() - data.timestamp < CACHE_TTL) { - return new Set(data.threats); + return { + threats: new Set(data.threats), + ranges: data.ranges || [] + }; } } catch { /* ignore */ } return null; } -function writeCache(threats) { +function writeCache(threats, ranges) { try { if (!existsSync(CACHE_DIR)) { mkdirSync(CACHE_DIR, { recursive: true }); } writeFileSync(join(CACHE_DIR, 'threats.json'), JSON.stringify({ timestamp: Date.now(), - threats: [...threats] + threats: [...threats], + ranges: ranges })); } catch { /* ignore */ } } -async function fetchOSV() { +async function fetchOSV(projectDependencies) { + // Query OSV batch API for the project's actual dependencies + if (!projectDependencies || projectDependencies.length === 0) { + return new Set(); + } + + const malicious = new Set(); + try { - const response = await fetch('https://osv-vulnerabilities.storage.googleapis.com/npm/all.zip', { - signal: AbortSignal.timeout(10000) - }); - if (!response.ok) return new Set(); + // Process in batches of 1000 + const batchSize = 1000; + for (let i = 0; i < projectDependencies.length; i += batchSize) { + const batch = projectDependencies.slice(i, i + batchSize); + const response = await fetch('https://api.osv.dev/v1/querybatch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + queries: batch.map(dep => ({ + package: { name: dep.name, ecosystem: 'npm' }, + version: dep.version + })) + }), + signal: AbortSignal.timeout(30000) + }); - const buffer = await response.arrayBuffer(); - const text = new TextDecoder().decode(buffer); - const malicious = new Set(); + if (!response.ok) continue; - // Parse JSONL format looking for MALWARE type - for (const line of text.split('\n')) { - if (!line.trim()) continue; - try { - const vuln = JSON.parse(line); - if (vuln.database_specific?.type === 'MALWARE' && vuln.affected) { - for (const affected of vuln.affected) { - if (affected.package?.ecosystem === 'npm' && affected.package?.name) { - const versions = affected.versions || []; - for (const v of versions) { - malicious.add(`${affected.package.name}@${v}`); + const data = await response.json(); + for (const result of data.results || []) { + for (const vuln of result.vulns || []) { + const summary = [vuln.summary || '', vuln.details || ''].join(' ').toLowerCase(); + const isMalware = summary.includes('malware') || + summary.includes('malicious') || + summary.includes('compromised') || + summary.includes('supply chain') || + summary.includes('backdoor'); + + if (isMalware && vuln.affected) { + for (const affected of vuln.affected) { + if (affected.package?.ecosystem === 'npm' && affected.package?.name) { + const versions = affected.versions || []; + for (const v of versions) { + malicious.add(`${affected.package.name}@${v}`); + } } } } } - } catch { /* skip invalid lines */ } + } } - return malicious; } catch { - return new Set(); + // Ignore errors, return what we have } + + return malicious; } async function fetchGitHubAdvisory() { @@ -284,94 +311,73 @@ async function main() { console.log(` Checking ${packages.length} packages (${fromPackageJson} from package.json, ${fromLockfile} from lockfile)\n`); // Try to use cache first - let threats = readCache(); - let fromCache = true; + const cache = readCache(); + let threats; + let ghRanges; - if (!threats) { - fromCache = false; + if (!cache) { console.log(' Fetching latest security databases...\n'); const [osvThreats, ghThreats] = await Promise.all([ - fetchOSV().then(r => { console.log(` šŸ“” OSV: ${r.size} malware entries`); return r; }), + fetchOSV(packages).then(r => { console.log(` šŸ“” OSV: ${r.size} malware entries`); return r; }), fetchGitHubAdvisory().then(r => { console.log(` šŸ“” GitHub Advisory: ${r.size} malware entries`); return r; }) ]); threats = new Set([...KNOWN_MALICIOUS, ...osvThreats]); + ghRanges = [...ghThreats]; - // Store GitHub advisories separately (they have version ranges) - const ghRanges = [...ghThreats]; - - // Check packages against exact matches and ranges - const found = []; - - for (const pkg of packages) { - const exact = `${pkg.name}@${pkg.version}`; - - // Check exact match - if (threats.has(exact)) { - found.push({ ...pkg, source: 'exact match' }); - continue; - } - - // Check GitHub Advisory ranges - for (const entry of ghRanges) { - const [name, range] = entry.split(':'); - if (pkg.name === name && parseVersionRange(range, pkg.version)) { - found.push({ ...pkg, source: 'GitHub Advisory' }); - break; - } - } - } - - if (found.length > 0) { - console.log('\n' + '!'.repeat(70)); - console.log('🚨 MALICIOUS PACKAGES DETECTED - BLOCKING INSTALLATION'); - console.log('!'.repeat(70) + '\n'); - - for (const pkg of found) { - console.log(` āŒ ${pkg.name}@${pkg.version} (${pkg.source})`); - } - - console.log('\nThese packages are known to contain malware or malicious code.'); - console.log('Installation has been blocked to protect your system.\n'); - console.log('Actions:'); - console.log(' 1. Remove these packages from package.json'); - console.log(' 2. Find safe alternatives'); - console.log(' 3. Run npm install again\n'); - console.log('='.repeat(70) + '\n'); - - process.exit(1); - } - - // Cache the results - writeCache(threats); - console.log(`\nāœ… Security check passed (${threats.size + ghRanges.length} known threats checked)`); + // Cache the results including ranges + writeCache(threats, ghRanges); } else { - // Quick check against cached threats - const found = []; - for (const pkg of packages) { - const exact = `${pkg.name}@${pkg.version}`; - if (threats.has(exact)) { - found.push(pkg); - } - } - - if (found.length > 0) { - console.log('\n' + '!'.repeat(70)); - console.log('🚨 MALICIOUS PACKAGES DETECTED - BLOCKING INSTALLATION'); - console.log('!'.repeat(70) + '\n'); - - for (const pkg of found) { - console.log(` āŒ ${pkg.name}@${pkg.version}`); - } - - console.log('\n='.repeat(70) + '\n'); - process.exit(1); - } - - console.log(`āœ… Security check passed (cached, ${threats.size} known threats)`); + threats = cache.threats; + ghRanges = cache.ranges; + console.log(` Using cached security database (${threats.size} exact + ${ghRanges.length} ranges)\n`); } + // Check packages against exact matches and ranges + const found = []; + + for (const pkg of packages) { + const exact = `${pkg.name}@${pkg.version}`; + + // Check exact match + if (threats.has(exact)) { + found.push({ ...pkg, source: 'exact match' }); + continue; + } + + // Check GitHub Advisory ranges + for (const entry of ghRanges) { + const [name, range] = entry.split(':'); + if (pkg.name === name && parseVersionRange(range, pkg.version)) { + found.push({ ...pkg, source: 'GitHub Advisory' }); + break; + } + } + } + + if (found.length > 0) { + console.log('\n' + '!'.repeat(70)); + console.log('🚨 MALICIOUS PACKAGES DETECTED - BLOCKING INSTALLATION'); + console.log('!'.repeat(70) + '\n'); + + for (const pkg of found) { + console.log(` āŒ ${pkg.name}@${pkg.version} (${pkg.source})`); + } + + console.log('\nThese packages are known to contain malware or malicious code.'); + console.log('Installation has been blocked to protect your system.\n'); + console.log('Actions:'); + console.log(' 1. Remove these packages from package.json'); + console.log(' 2. Find safe alternatives'); + console.log(' 3. Run npm install again\n'); + console.log('='.repeat(70) + '\n'); + + process.exit(1); + } + + console.log(`\nāœ… Security check passed (${threats.size} exact + ${ghRanges.length} ranges checked)`) + console.log('='.repeat(70) + '\n'); }