From 0fa8672f1602c91aa8c761cb475d5e41bd8e3f18 Mon Sep 17 00:00:00 2001 From: VitoAlbano Date: Mon, 1 Jun 2026 10:31:11 +0100 Subject: [PATCH] [AAE-46514] - Improved check, updated descriptions, skip check on CI as there is already Sonar --- .github/workflows/build-lib-workflow.yml | 1 + .github/workflows/pull-request.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/unit-test-workflow.yml | 3 + README.md | 16 +++++- scripts/check-security.mjs | 6 +- scripts/preinstall-check.mjs | 6 +- .../security-providers/meterian-provider.mjs | 8 ++- scripts/security-providers/osv-provider.mjs | 56 ++++++++++++++++--- 9 files changed, 81 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-lib-workflow.yml b/.github/workflows/build-lib-workflow.yml index aca14a6516..a9afab4804 100644 --- a/.github/workflows/build-lib-workflow.yml +++ b/.github/workflows/build-lib-workflow.yml @@ -11,6 +11,7 @@ on: env: NODE_OPTIONS: "--max-old-space-size=5120" + ADF_SKIP_SECURITY_CHECK: 1 jobs: build: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 09d12fc54e..82934aea11 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,6 +33,7 @@ concurrency: env: GH_COMMIT: ${{ github.sha }} NODE_OPTIONS: "--max-old-space-size=5120" + ADF_SKIP_SECURITY_CHECK: 1 jobs: pre-checks: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41ee36ae7a..8df70ebc12 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,6 +40,7 @@ env: GH_BUILD_NUMBER: ${{ github.run_id }} LOG_LEVEL: "ERROR" NODE_OPTIONS: "--max-old-space-size=5120" + ADF_SKIP_SECURITY_CHECK: 1 jobs: setup: diff --git a/.github/workflows/unit-test-workflow.yml b/.github/workflows/unit-test-workflow.yml index 9f182d01d5..8da47c5b08 100644 --- a/.github/workflows/unit-test-workflow.yml +++ b/.github/workflows/unit-test-workflow.yml @@ -9,6 +9,9 @@ on: type: string default: 'develop' +env: + ADF_SKIP_SECURITY_CHECK: 1 + jobs: generate-affected-matrix: name: "Generate affected matrix" diff --git a/README.md b/README.md index 808da6e4d0..f8305d6fba 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,22 @@ This project has built-in supply chain attack protection. When you run `npm inst 1. **Preinstall check** scans both `package.json` AND `package-lock.json` against OSV + GitHub Advisory databases 2. If malicious packages detected → installation blocked BEFORE any code runs -3. Packages install normally -4. **Post-install check** verifies installed packages (defense in depth) -5. Only trusted packages (esbuild, nx, husky, etc.) get their native bindings rebuilt +3. Packages install with `--ignore-scripts` (lifecycle scripts disabled for security) +4. **Post-install check** re-runs the security scan (defense in depth) and deletes `node_modules` if violations found +5. Trusted packages (esbuild, nx, husky, etc.) are rebuilt via `npm rebuild` to run their lifecycle scripts Checking `package.json` catches new dependencies added during upgrades (e.g., `nx migrate`) before the lockfile is updated. If a malicious package is detected at any stage, installation is blocked. +To disable security checks (e.g., in CI environments): +```bash +ADF_SKIP_SECURITY_CHECK=1 npm install +``` + +To keep `node_modules` on violations (for debugging): +```bash +ADF_SECURITY_KEEP_NODE_MODULES=1 npm install +``` + ## Components You can find the sources for all ADF components in the [`lib`](/lib) folder. diff --git a/scripts/check-security.mjs b/scripts/check-security.mjs index 4eee776d90..281ff370c5 100644 --- a/scripts/check-security.mjs +++ b/scripts/check-security.mjs @@ -489,6 +489,8 @@ async function main() { } main().catch(error => { - console.error('Security check failed:', error.message); - process.exit(0); + console.error('\nāŒ Security check crashed unexpectedly:', error.message); + console.error(' Blocking installation as a precaution.'); + console.error(' Set ADF_SKIP_SECURITY_CHECK=1 to bypass.\n'); + process.exit(1); }); diff --git a/scripts/preinstall-check.mjs b/scripts/preinstall-check.mjs index 3f417ab987..eeaf13ee82 100644 --- a/scripts/preinstall-check.mjs +++ b/scripts/preinstall-check.mjs @@ -455,6 +455,8 @@ async function main() { } main().catch(error => { - console.error('Security check error:', error.message); - process.exit(0); + console.error('\nāŒ Security check crashed unexpectedly:', error.message); + console.error(' Blocking installation as a precaution.'); + console.error(' Set ADF_SKIP_SECURITY_CHECK=1 to bypass.\n'); + process.exit(1); }); diff --git a/scripts/security-providers/meterian-provider.mjs b/scripts/security-providers/meterian-provider.mjs index 1874c55458..3e09a14948 100644 --- a/scripts/security-providers/meterian-provider.mjs +++ b/scripts/security-providers/meterian-provider.mjs @@ -39,6 +39,10 @@ function listDirectories(path) { } function findVSCodeExtensionCli(homeDir) { + if (!homeDir) { + return null; + } + const extensionsDir = join(homeDir, '.vscode', 'extensions'); const extensions = listDirectories(extensionsDir) .filter(dir => /^meterian\.meterian-heidi-\d+\.\d+\.\d+$/.test(dir)) @@ -58,9 +62,9 @@ function findVSCodeExtensionCli(homeDir) { function findGlobalCli(homeDir, rootDir) { const possibleLocations = [ join(rootDir, 'node_modules', '@meterian', 'cli'), - join(homeDir, '.npm-global', 'lib', 'node_modules', '@meterian', 'cli'), + homeDir ? join(homeDir, '.npm-global', 'lib', 'node_modules', '@meterian', 'cli') : null, '/usr/local/lib/node_modules/@meterian/cli' - ]; + ].filter(Boolean); for (const location of possibleLocations) { if (isDirectory(location)) { diff --git a/scripts/security-providers/osv-provider.mjs b/scripts/security-providers/osv-provider.mjs index b4192e91fa..b39f0ebba2 100644 --- a/scripts/security-providers/osv-provider.mjs +++ b/scripts/security-providers/osv-provider.mjs @@ -36,18 +36,51 @@ function isMalwareRelated(text) { return MALWARE_KEYWORDS.some(keyword => lowerText.includes(keyword.toLowerCase())); } +function extractRangesFromEvents(events) { + const ranges = []; + let introduced = null; + + for (const event of events) { + if (event.introduced !== undefined) { + introduced = event.introduced; + } else if (event.fixed !== undefined && introduced !== null) { + if (introduced === '0') { + ranges.push(`< ${event.fixed}`); + } else { + ranges.push(`>= ${introduced}, < ${event.fixed}`); + } + introduced = null; + } else if (event.last_affected !== undefined && introduced !== null) { + if (introduced === '0') { + ranges.push(`<= ${event.last_affected}`); + } else { + ranges.push(`>= ${introduced}, <= ${event.last_affected}`); + } + introduced = null; + } + } + + if (introduced !== null && introduced !== '0') { + ranges.push(`>= ${introduced}`); + } + + return ranges; +} + function extractVersionsFromVulnerability(vulnerability) { const affectedEntries = vulnerability.affected || []; - const introducedVersions = affectedEntries + const versionRanges = affectedEntries .flatMap(entry => entry.ranges || []) - .flatMap(range => range.events || []) - .filter(event => event.introduced && event.introduced !== '0') - .map(event => event.introduced); + .filter(range => range.type === 'SEMVER' || range.type === 'ECOSYSTEM') + .flatMap(range => extractRangesFromEvents(range.events || [])); const explicitVersions = affectedEntries.flatMap(entry => entry.versions || []); - return [...new Set([...introducedVersions, ...explicitVersions])]; + return { + versions: [...new Set(explicitVersions)], + versionRanges: [...new Set(versionRanges)] + }; } function splitIntoBatches(items, batchSize) { @@ -95,14 +128,15 @@ function extractMalwareInfo(vulnerability) { return null; } - const versions = extractVersionsFromVulnerability(vulnerability); - if (versions.length === 0) { + const { versions, versionRanges } = extractVersionsFromVulnerability(vulnerability); + if (versions.length === 0 && versionRanges.length === 0) { return null; } return { packageName, versions, + versionRanges, reason: vulnerability.summary || 'Malware detected by OSV' }; } @@ -116,9 +150,15 @@ function processBatchResults(batchResults, malwareRegistry) { if (malwareInfo) { if (!malwareRegistry[malwareInfo.packageName]) { - malwareRegistry[malwareInfo.packageName] = { versions: [], reason: '', source: 'OSV' }; + malwareRegistry[malwareInfo.packageName] = { + versions: [], + versionRanges: [], + reason: '', + source: 'OSV' + }; } malwareRegistry[malwareInfo.packageName].versions.push(...malwareInfo.versions); + malwareRegistry[malwareInfo.packageName].versionRanges.push(...malwareInfo.versionRanges); malwareRegistry[malwareInfo.packageName].reason = malwareInfo.reason; } }