diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8c9e60a9..56df79dd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,20 +152,18 @@ jobs: - name: Run npm audit working-directory: frontend run: | - # Only audit production dependencies and filter out npm-internal packages - # (npm audit reports vulnerabilities in its own bundled deps like tar/minimatch). + # Only audit production dependencies and filter out npm-internal packages. + # npm 10.x audit/ls reports vulns in its own bundled deps (npm, tar, minimatch) + # so we parse package-lock.json directly to get the real prod dep list. npm audit --omit=dev --json > /tmp/audit.json 2>/dev/null || true - npm ls --omit=dev --all --json 2>/dev/null > /tmp/deps.json || true python3 -c " - import json, sys, subprocess + import json, sys data = json.load(open('/tmp/audit.json')) - deps = json.load(open('/tmp/deps.json')) + lock = json.load(open('package-lock.json')) prod = set() - def walk(obj): - for name, info in (obj.get('dependencies') or {}).items(): - prod.add(name) - walk(info) - walk(deps) + for path, info in lock.get('packages', {}).items(): + if path and not info.get('dev') and not info.get('devOptional'): + prod.add(path.split('node_modules/')[-1]) vulns = data.get('vulnerabilities', {}) fixable = {n: v for n, v in vulns.items() if n in prod and v.get('severity') in ('high', 'critical') and v.get('fixAvailable')} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 9e0e8cb5c..0c2f859fd 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -278,23 +278,21 @@ jobs: working-directory: frontend run: | npm audit --omit=dev --json > npm-audit-raw.json 2>/dev/null || true - # Filter audit results to only include actual project dependencies - # (npm audit sometimes reports vulnerabilities in the npm CLI itself) + # Filter audit results to only include actual project dependencies. + # npm 10.x audit/ls reports vulns in its own bundled deps (npm, tar, minimatch) + # so we parse package-lock.json directly to get the real prod dep list. node -e " const fs = require('fs'); const raw = fs.readFileSync('npm-audit-raw.json', 'utf8'); let results; try { results = JSON.parse(raw); } catch { results = { vulnerabilities: {} }; } - const depTree = JSON.parse(require('child_process').execSync( - 'npm ls --omit=dev --all --json 2>/dev/null', { encoding: 'utf8' } - )); + const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); const prodDeps = new Set(); - (function walk(obj) { - for (const [name, info] of Object.entries(obj.dependencies || {})) { - prodDeps.add(name); - walk(info); + for (const [path, info] of Object.entries(lock.packages || {})) { + if (path && !info.dev && !info.devOptional) { + prodDeps.add(path.split('node_modules/').pop()); } - })(depTree); + } const vulns = results.vulnerabilities || {}; const filtered = {}; for (const [name, info] of Object.entries(vulns)) {