[AAE-46514] - Fixed copilot comments

This commit is contained in:
VitoAlbano
2026-06-04 09:26:32 +01:00
parent c06e827d93
commit 31afc6f612
2 changed files with 173 additions and 67 deletions
+169 -63
View File
@@ -33,7 +33,7 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { execSync, spawnSync } from 'child_process';
import { spawnSync } from 'child_process';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = join(__dirname, '..');
@@ -132,44 +132,55 @@ function extractVersionsFromOSV(vuln) {
return [...new Set(versions)];
}
async function fetchFromOSV(packages) {
async function fetchFromOSV(projectDependencies) {
console.log(' 📡 Fetching from OSV (Google)...');
const results = {};
try {
const response = await fetch(OSV_BATCH_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
queries: Object.keys(packages).map(name => ({
package: { name, ecosystem: 'npm' }
}))
})
});
if (!projectDependencies || projectDependencies.length === 0) {
console.log(' ⚠ OSV: No dependencies to check');
return results;
}
if (response.ok) {
const data = await response.json();
for (const result of data.results || []) {
for (const vuln of result.vulns || []) {
const summary = [vuln.summary || '', vuln.details || ''].join(' ');
if (isMalwareRelated(summary)) {
const pkgName = vuln.affected?.[0]?.package?.name;
if (pkgName) {
const versions = extractVersionsFromOSV(vuln);
if (versions.length > 0) {
if (!results[pkgName]) {
results[pkgName] = { versions: [], reason: '' };
try {
// Query OSV for the project's actual dependencies (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(OSV_BATCH_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
queries: batch.map(dep => ({
package: { name: dep.name, ecosystem: 'npm' },
version: dep.version
}))
})
});
if (response.ok) {
const data = await response.json();
for (const result of data.results || []) {
for (const vuln of result.vulns || []) {
const summary = [vuln.summary || '', vuln.details || ''].join(' ');
if (isMalwareRelated(summary)) {
const pkgName = vuln.affected?.[0]?.package?.name;
if (pkgName) {
const versions = extractVersionsFromOSV(vuln);
if (versions.length > 0) {
if (!results[pkgName]) {
results[pkgName] = { versions: [], reason: '' };
}
results[pkgName].versions.push(...versions);
results[pkgName].reason = vuln.summary || 'Malware detected by OSV';
results[pkgName].source = 'OSV';
}
results[pkgName].versions.push(...versions);
results[pkgName].reason = vuln.summary || 'Malware detected by OSV';
results[pkgName].source = 'OSV';
}
}
}
}
}
console.log(` ✓ OSV: Found ${Object.keys(results).length} malware entries`);
}
console.log(` ✓ OSV: Found ${Object.keys(results).length} malware entries`);
} catch (e) {
console.log(` ⚠ OSV: Could not fetch (${e.message})`);
}
@@ -275,7 +286,7 @@ async function checkWithMeterian(dependencies) {
}
}
async function fetchFromGitHubAdvisory(packages) {
async function fetchFromGitHubAdvisory() {
console.log(' 📡 Fetching from GitHub Advisory...');
const results = {};
@@ -301,28 +312,23 @@ async function fetchFromGitHubAdvisory(packages) {
for (const vuln of advisory.vulnerabilities || []) {
const pkgName = vuln.package?.name;
if (pkgName && vuln.package?.ecosystem === 'npm') {
// Extract affected versions
const versions = [];
if (vuln.vulnerable_version_range) {
// Parse version range like "= 1.95.6" or ">= 1.0.0, < 1.0.1"
const exactMatch = vuln.vulnerable_version_range.match(/^= (.+)$/);
if (exactMatch) {
versions.push(exactMatch[1]);
}
}
if (vuln.first_patched_version?.identifier) {
// This tells us the fixed version, not the bad ones
// We'd need to know the introduced version too
if (!results[pkgName]) {
results[pkgName] = { versions: [], versionRanges: [], reason: '' };
}
if (versions.length > 0 || isMalwareRelated(summary)) {
if (!results[pkgName]) {
results[pkgName] = { versions: [], reason: '' };
if (vuln.vulnerable_version_range) {
// Store the full version range for later matching
results[pkgName].versionRanges.push(vuln.vulnerable_version_range);
// Also extract exact versions where possible
const exactMatch = vuln.vulnerable_version_range.match(/^=\s*(.+)$/);
if (exactMatch) {
results[pkgName].versions.push(exactMatch[1].trim());
}
results[pkgName].versions.push(...versions);
results[pkgName].reason = advisory.summary || 'Malware detected by GitHub Advisory';
results[pkgName].source = 'GitHub';
}
results[pkgName].reason = advisory.summary || 'Malware detected by GitHub Advisory';
results[pkgName].source = 'GitHub';
}
}
}
@@ -337,7 +343,51 @@ async function fetchFromGitHubAdvisory(packages) {
return results;
}
async function fetchMalwareList() {
function getProjectDependencies() {
const dependencies = [];
const lockfilePath = join(ROOT_DIR, 'package-lock.json');
if (existsSync(lockfilePath)) {
try {
const lockfile = JSON.parse(readFileSync(lockfilePath, 'utf8'));
if (lockfile.packages) {
for (const [path, info] of Object.entries(lockfile.packages)) {
if (!path || path === '' || !info.version) continue;
const name = path.replace(/^node_modules\//, '').replace(/^.*node_modules\//, '');
dependencies.push({ name, version: info.version });
}
}
} catch {
// Ignore parse errors
}
}
// Also check package.json for dependencies not yet in lockfile
const packageJsonPath = join(ROOT_DIR, 'package.json');
if (existsSync(packageJsonPath)) {
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
for (const [name, versionSpec] of Object.entries(allDeps)) {
if (typeof versionSpec === 'string' && !versionSpec.startsWith('file:')) {
const version = versionSpec.replace(/^[\^~>=<]*/, '').split(' ')[0];
if (version && !dependencies.some(d => d.name === name && d.version === version)) {
dependencies.push({ name, version });
}
}
}
} catch {
// Ignore parse errors
}
}
return dependencies;
}
async function fetchMalwareList(projectDependencies) {
// Check cache first
if (existsSync(CACHE_FILE)) {
try {
@@ -345,7 +395,7 @@ async function fetchMalwareList() {
if (Date.now() - cache.timestamp < CACHE_TTL_MS) {
console.log(' Using cached security database...');
console.log(` (${cache.sources?.join(', ') || 'fallback'} - cached ${Math.round((Date.now() - cache.timestamp) / 60000)} min ago)`);
return cache.packages;
return { packages: cache.packages, sources: cache.sources || ['fallback'] };
}
} catch {
// Cache corrupted, will refetch
@@ -358,8 +408,8 @@ async function fetchMalwareList() {
const packages = { ...FALLBACK_BLOCKED_PACKAGES };
const sources = ['fallback'];
// Fetch from OSV
const osvResults = await fetchFromOSV(packages);
// Fetch from OSV using the project's actual dependencies
const osvResults = await fetchFromOSV(projectDependencies);
if (Object.keys(osvResults).length > 0) {
sources.push('OSV');
for (const [name, data] of Object.entries(osvResults)) {
@@ -372,14 +422,15 @@ async function fetchMalwareList() {
}
}
// Fetch from GitHub Advisory
const ghResults = await fetchFromGitHubAdvisory(packages);
// Fetch from GitHub Advisory (returns all known npm malware, limited to first page)
const ghResults = await fetchFromGitHubAdvisory();
if (Object.keys(ghResults).length > 0) {
sources.push('GitHub');
for (const [name, data] of Object.entries(ghResults)) {
if (packages[name]) {
// Merge versions
packages[name].versions = [...new Set([...packages[name].versions, ...data.versions])];
packages[name].versions = [...new Set([...packages[name].versions, ...(data.versions || [])])];
packages[name].versionRanges = [...new Set([...(packages[name].versionRanges || []), ...(data.versionRanges || [])])];
} else {
packages[name] = data;
}
@@ -402,7 +453,7 @@ async function fetchMalwareList() {
// Cache write failed, continue anyway
}
return packages;
return { packages, sources };
}
// ============================================================================
@@ -421,8 +472,61 @@ function readJsonFile(filePath) {
}
}
function checkVersion(version, blockedVersions) {
return blockedVersions.some(blocked => blocked === version);
function compareVersions(a, b) {
const partsA = a.replace(/^v/, '').split('.').map(p => parseInt(p, 10) || 0);
const partsB = b.replace(/^v/, '').split('.').map(p => parseInt(p, 10) || 0);
const len = Math.max(partsA.length, partsB.length);
for (let i = 0; i < len; i++) {
const numA = partsA[i] || 0;
const numB = partsB[i] || 0;
if (numA > numB) return 1;
if (numA < numB) return -1;
}
return 0;
}
function matchesVersionRange(version, range) {
// Parse version range like ">= 1.0.0, < 1.0.1" or "= 1.95.6"
const conditions = range.split(',').map(c => c.trim());
for (const condition of conditions) {
const match = condition.match(/^(>=|<=|>|<|=)?\s*(.+)$/);
if (!match) continue;
const [, operator = '=', targetVersion] = match;
const cmp = compareVersions(version, targetVersion.trim());
switch (operator) {
case '=':
if (cmp !== 0) return false;
break;
case '>':
if (cmp <= 0) return false;
break;
case '>=':
if (cmp < 0) return false;
break;
case '<':
if (cmp >= 0) return false;
break;
case '<=':
if (cmp > 0) return false;
break;
}
}
return true;
}
function checkVersion(version, blockedEntry) {
// Check exact version matches
if (blockedEntry.versions?.some(blocked => blocked === version)) {
return true;
}
// Check version ranges (from GitHub Advisory)
if (blockedEntry.versionRanges?.some(range => matchesVersionRange(version, range))) {
return true;
}
return false;
}
function checkDependencies(deps, blockedPackages, source) {
@@ -432,7 +536,7 @@ function checkDependencies(deps, blockedPackages, source) {
for (const [name, versionSpec] of Object.entries(deps)) {
if (blockedPackages[name]) {
const version = versionSpec.replace(/^[\^~>=<]*/, '').split(' ')[0];
if (checkVersion(version, blockedPackages[name].versions)) {
if (checkVersion(version, blockedPackages[name])) {
violations.push({
package: name,
version,
@@ -454,7 +558,7 @@ function checkLockfileDependencies(packages, blockedPackages) {
const name = path.replace(/^node_modules\//, '').replace(/^.*node_modules\//, '');
if (blockedPackages[name] && info.version) {
if (checkVersion(info.version, blockedPackages[name].versions)) {
if (checkVersion(info.version, blockedPackages[name])) {
violations.push({
package: name,
version: info.version,
@@ -477,9 +581,9 @@ async function main() {
console.log('='.repeat(70));
console.log('Scanning for known supply chain attacks and compromised packages...\n');
// Fetch blocked packages list
const blockedPackages = await fetchMalwareList();
const threatCount = Object.keys(blockedPackages).length;
// Get project dependencies and fetch blocked packages list
const projectDependencies = getProjectDependencies();
const { packages: blockedPackages, sources } = await fetchMalwareList(projectDependencies);
const violations = [];
@@ -572,7 +676,9 @@ async function main() {
process.exit(1);
}
console.log(`✅ Security check passed (${threatCount} known threats checked)`);
const packageCount = Object.keys(blockedPackages).length;
const sourceList = sources.join(' + ');
console.log(`✅ Security check passed (checked against ${sourceList}, ${packageCount} blocked packages)`);
console.log('='.repeat(70) + '\n');
process.exit(0);
}
+4 -4
View File
@@ -20,9 +20,9 @@
/**
* Post-install Security Script
*
* This runs after `npm install` / `npm ci` (via prepare hook).
* Preinstall already checked the lockfile - this is a defense-in-depth
* check against installed packages + rebuilds trusted native bindings.
* Runs after `npm install` / `npm ci` via the prepare hook.
* The preinstall hook already checked package.json and lockfile - this is
* a defense-in-depth check against installed packages.
*
* This script:
* 1. Runs security check against OSV + GitHub Advisory (defense in depth)
@@ -138,7 +138,7 @@ async function main() {
trustedInstalled.forEach(pkg => console.log(`${pkg}`));
console.log('');
run(`npm rebuild ${trustedInstalled.join(' ')}`);
run(`npm rebuild --ignore-scripts=false ${trustedInstalled.join(' ')}`);
} else {
console.log('No trusted packages require rebuilding.\n');
}