import { execSync } from 'child_process'; import fs from 'fs/promises'; import { Command } from 'commander'; export interface SecurityAgentOptions {} export interface SecurityAgentResult { /** Nombre total de vulnérabilités détectées */ total: number; /** Nombre de vulnérabilités corrigeables */ fixable: number; /** Répartition des vulnérabilités par sévérité */ bySeverity: { critical: number; high: number; moderate: number; low: number }; /** Top 5 des paquets les plus vulnérables */ topPackages: Array<{ package: string; count: number }>; /** Chemin vers le rapport brut (audit-report.json) */ detailPath: string; /** Liste brute des vulnérabilités */ vulnerabilities: unknown[]; } /** * Analyse les vulnérabilités via pnpm audit. */ export async function securityAgent( _opts?: SecurityAgentOptions, ): Promise { const detailPath = 'audit-report.json'; try { const cmd = 'pnpm audit --json'; const stdout = execSync(cmd, { encoding: 'utf-8' }); await fs.writeFile(detailPath, stdout, 'utf-8'); const report: any = JSON.parse(stdout); const vulns: any[] = Array.isArray(report.vulnerabilities) ? report.vulnerabilities : []; const total = vulns.length; const fixable = vulns.filter(v => v.fix || v.fixAvailable).length; const bySeverity = { critical: 0, high: 0, moderate: 0, low: 0 }; vulns.forEach(v => { const s = (v.severity as string)?.toLowerCase(); if (s in bySeverity) { // @ts-expect-error allow indexing by dynamic severity key bySeverity[s]++; } }); const pkgCounts: Record = {}; vulns.forEach(v => { const pkg = v.package || v.packageName || v.name; if (pkg) { pkgCounts[pkg] = (pkgCounts[pkg] || 0) + 1; } }); const topPackages = Object.entries(pkgCounts) .map(([packageName, count]) => ({ package: packageName, count })) .sort((a, b) => b.count - a.count) .slice(0, 5); return { total, fixable, bySeverity, topPackages, detailPath, vulnerabilities: vulns }; } catch (err: any) { let raw = ''; if (err.stdout) raw = err.stdout as string; if (!raw && err.stderr) raw = err.stderr as string; await fs.writeFile(detailPath, raw, 'utf-8'); let vulns: any[] = []; try { const report: any = JSON.parse(raw); vulns = Array.isArray(report.vulnerabilities) ? report.vulnerabilities : []; } catch {} const total = vulns.length; const fixable = vulns.filter(v => v.fix || v.fixAvailable).length; const bySeverity = { critical: 0, high: 0, moderate: 0, low: 0 }; vulns.forEach(v => { const s = (v.severity as string)?.toLowerCase(); if (s in bySeverity) { // @ts-expect-error allow indexing by dynamic severity key bySeverity[s]++; } }); const pkgCounts: Record = {}; vulns.forEach(v => { const pkg = v.package || v.packageName || v.name; if (pkg) { pkgCounts[pkg] = (pkgCounts[pkg] || 0) + 1; } }); const topPackages = Object.entries(pkgCounts) .map(([packageName, count]) => ({ package: packageName, count })) .sort((a, b) => b.count - a.count) .slice(0, 5); return { total, fixable, bySeverity, topPackages, detailPath, vulnerabilities: vulns }; } } export const cli = { command: 'qualimetrie:security', description: 'Analyse les vulnérabilités de dépendances', builder: (cmd: Command) => cmd, handler: async (_opts: unknown) => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { securityAgent: run } = require('./securityAgent'); const res = await run(); console.log(res); }, };