verimu 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@ The NPM package for `verimu`, a tool for producing CRA-compliant SBOMs via CI /
|
|
|
4
4
|
|
|
5
5
|
## Current and Planned Support
|
|
6
6
|
|
|
7
|
-
- [
|
|
7
|
+
- [x] GitHub Actions
|
|
8
8
|
- [ ] GitLab CI
|
|
9
9
|
- [ ] Bitbucket Pipelines
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
## Currently Supported Package Managers
|
|
13
13
|
|
|
14
|
-
- [
|
|
14
|
+
- [x] npm
|
|
15
15
|
- [ ] pip
|
|
16
16
|
- [ ] go
|
|
17
17
|
- [ ] cargo
|
package/dist/index.cjs
CHANGED
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/generate-sbom.ts","../src/scan.ts","../src/scanners/npm/npm-scanner.ts","../src/core/errors.ts","../src/scanners/nuget/nuget-scanner.ts","../src/scanners/cargo/cargo-scanner.ts","../src/scanners/registry.ts","../src/sbom/cyclonedx.ts","../src/cve/osv.ts","../src/cve/aggregator.ts","../src/reporters/console.ts"],"sourcesContent":["// ─── Primary API ────────────────────────────────────────────────\n//\n// import { scan, generateSbom, shouldFailCi } from 'verimu'\n//\n\nexport { generateSbom } from './generate-sbom.js';\nexport { scan, shouldFailCi, printReport } from './scan.js';\n\n// ─── Types ──────────────────────────────────────────────────────\n\nexport type {\n // generateSbom() types\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n\n // scan() types\n Dependency,\n ScanResult,\n Sbom,\n SbomFormat,\n Severity,\n Vulnerability,\n VulnerabilitySource,\n CveCheckResult,\n VerimuReport,\n VerimuConfig,\n Ecosystem,\n CiProvider,\n} from './core/types.js';\n\n// ─── Errors ─────────────────────────────────────────────────────\n\nexport {\n VerimuError,\n NoLockfileError,\n LockfileParseError,\n CveSourceError,\n ApiKeyRequiredError,\n} from './core/errors.js';\n\n// ─── Advanced / Internal ────────────────────────────────────────\n// For users who need fine-grained control over individual steps.\n\nexport { NpmScanner } from './scanners/npm/npm-scanner.js';\nexport { ScannerRegistry } from './scanners/registry.js';\nexport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nexport { OsvSource } from './cve/osv.js';\nexport { CveAggregator } from './cve/aggregator.js';\nexport { ConsoleReporter } from './reporters/console.js';\n","import { randomUUID } from 'crypto';\nimport type {\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n Ecosystem,\n} from './core/types.js';\n\n/**\n * Generates an NTIA-compliant CycloneDX 1.7 SBOM from structured dependency data.\n *\n * This is a **pure function** — no filesystem access, no network calls, no side effects.\n * It takes a project name, version, and list of dependencies, and returns a complete\n * CycloneDX 1.7 JSON SBOM that passes NTIA minimum-element validation.\n *\n * @example\n * ```ts\n * import { generateSbom } from 'verimu';\n *\n * const result = generateSbom({\n * projectName: 'my-app',\n * projectVersion: '1.0.0',\n * dependencies: [\n * { name: 'express', version: '4.18.2', ecosystem: 'npm' },\n * { name: '@types/node', version: '20.11.5', ecosystem: 'npm', direct: false },\n * ],\n * });\n *\n * console.log(result.componentCount); // 2\n * console.log(result.content); // formatted JSON string\n * ```\n */\nexport function generateSbom(input: GenerateSbomInput): GenerateSbomResult {\n const {\n projectName,\n projectVersion = '0.0.0',\n dependencies,\n } = input;\n\n const timestamp = new Date().toISOString();\n\n // Resolve PURLs for any deps that don't have one\n const resolvedDeps = dependencies.map((dep) => ({\n ...dep,\n direct: dep.direct ?? true,\n purl: dep.purl ?? buildPurl(dep.name, dep.version, dep.ecosystem),\n }));\n\n const rootPurl = buildPurl(projectName, projectVersion, 'npm');\n\n const sbom = {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp,\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: '0.0.1',\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n { type: 'website', url: 'https://verimu.eu' },\n ],\n },\n ],\n },\n supplier: { name: projectName },\n component: {\n type: 'application',\n name: projectName,\n version: projectVersion,\n 'bom-ref': rootPurl,\n supplier: { name: projectName },\n },\n },\n components: resolvedDeps.map((dep) => ({\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n supplier: { name: deriveSupplierName(dep.name) },\n })),\n dependencies: [\n {\n ref: rootPurl,\n dependsOn: resolvedDeps.map((d) => d.purl),\n },\n ],\n };\n\n const content = JSON.stringify(sbom, null, 2);\n\n return {\n sbom,\n content,\n componentCount: resolvedDeps.length,\n specVersion: '1.7',\n generatedAt: timestamp,\n };\n}\n\n// ─── Internal helpers ───────────────────────────────────────────\n\nconst PURL_TYPE_MAP: Record<Ecosystem, string> = {\n npm: 'npm',\n nuget: 'nuget',\n cargo: 'cargo',\n maven: 'maven',\n pip: 'pypi',\n go: 'golang',\n};\n\n/**\n * Builds a Package URL (purl) per the purl spec.\n *\n * For npm scoped packages, the @ prefix is percent-encoded as %40:\n * @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n *\n * See: https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md\n */\nfunction buildPurl(name: string, version: string, ecosystem: Ecosystem): string {\n const type = PURL_TYPE_MAP[ecosystem] || ecosystem;\n\n if (ecosystem === 'npm' && name.startsWith('@')) {\n return `pkg:${type}/%40${name.slice(1)}@${version}`;\n }\n\n return `pkg:${type}/${name}@${version}`;\n}\n\n/**\n * Derives supplier name from a package name.\n * Scoped packages: \"@vue/reactivity\" → \"@vue\"\n * Unscoped packages: \"express\" → \"express\"\n */\nfunction deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n return packageName.split('/')[0];\n }\n return packageName;\n}\n","import { writeFile } from 'fs/promises';\nimport { ScannerRegistry } from './scanners/registry.js';\nimport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nimport { CveAggregator } from './cve/aggregator.js';\nimport { ConsoleReporter } from './reporters/console.js';\nimport type { VerimuConfig, VerimuReport, Severity } from './core/types.js';\n\n/**\n * Main scan pipeline — orchestrates the full Verimu workflow:\n * 1. Detect ecosystem & parse lockfile\n * 2. Generate CycloneDX SBOM\n * 3. Check dependencies for CVEs\n * 4. Produce report\n * 5. Optionally upload snapshot to Verimu API\n */\nexport async function scan(config: VerimuConfig): Promise<VerimuReport> {\n const {\n projectPath,\n sbomOutput = './sbom.cdx.json',\n skipCveCheck = false,\n } = config;\n\n // 1. Scan dependencies\n const registry = new ScannerRegistry();\n const scanResult = await registry.detectAndScan(projectPath);\n\n // 2. Generate SBOM\n const sbomGenerator = new CycloneDxGenerator();\n const sbom = sbomGenerator.generate(scanResult);\n\n // 3. Write SBOM to disk\n await writeFile(sbomOutput, sbom.content, 'utf-8');\n\n // 4. Check CVEs (unless skipped)\n let cveCheck;\n if (skipCveCheck) {\n cveCheck = {\n vulnerabilities: [],\n sourcesQueried: [],\n sourceErrors: [],\n checkDurationMs: 0,\n };\n } else {\n const aggregator = new CveAggregator();\n cveCheck = await aggregator.check(scanResult.dependencies);\n }\n\n // 5. Build report\n const summary = {\n totalDependencies: scanResult.dependencies.length,\n totalVulnerabilities: cveCheck.vulnerabilities.length,\n critical: cveCheck.vulnerabilities.filter((v) => v.severity === 'CRITICAL').length,\n high: cveCheck.vulnerabilities.filter((v) => v.severity === 'HIGH').length,\n medium: cveCheck.vulnerabilities.filter((v) => v.severity === 'MEDIUM').length,\n low: cveCheck.vulnerabilities.filter((v) => v.severity === 'LOW').length,\n exploitedInWild: cveCheck.vulnerabilities.filter((v) => v.exploitedInWild).length,\n };\n\n const report: VerimuReport = {\n project: {\n path: projectPath,\n ecosystem: scanResult.ecosystem,\n dependencyCount: scanResult.dependencies.length,\n },\n sbom,\n cveCheck,\n summary,\n generatedAt: new Date().toISOString(),\n };\n\n return report;\n}\n\n/**\n * Determines if the scan should fail CI based on severity threshold.\n */\nexport function shouldFailCi(report: VerimuReport, threshold: Severity): boolean {\n const severityOrder: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n const thresholdLevel = severityOrder[threshold] ?? 4;\n\n return report.cveCheck.vulnerabilities.some(\n (v) => severityOrder[v.severity] <= thresholdLevel\n );\n}\n\n/**\n * Prints a console report to stdout.\n */\nexport function printReport(report: VerimuReport): void {\n const reporter = new ConsoleReporter();\n console.log(reporter.report(report));\n}\n","import { readFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport type { DependencyScanner } from '../scanner.interface.js';\nimport type { Dependency, Ecosystem, ScanResult } from '../../core/types.js';\nimport { LockfileParseError } from '../../core/errors.js';\n\n/**\n * npm / Node.js dependency scanner.\n *\n * Parses package-lock.json (v2/v3 format) to extract the full\n * resolved dependency tree. Also reads package.json to determine\n * which dependencies are direct vs transitive.\n */\nexport class NpmScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'npm';\n readonly lockfileNames = ['package-lock.json'];\n\n async detect(projectPath: string): Promise<string | null> {\n const lockfilePath = path.join(projectPath, 'package-lock.json');\n return existsSync(lockfilePath) ? lockfilePath : null;\n }\n\n async scan(projectPath: string, lockfilePath: string): Promise<ScanResult> {\n const [lockfileRaw, packageJsonRaw] = await Promise.all([\n readFile(lockfilePath, 'utf-8'),\n readFile(path.join(projectPath, 'package.json'), 'utf-8').catch(() => null),\n ]);\n\n let lockfile: NpmLockfile;\n try {\n lockfile = JSON.parse(lockfileRaw);\n } catch {\n throw new LockfileParseError(lockfilePath, 'Invalid JSON');\n }\n\n // Determine direct dependency names from package.json\n const directNames = new Set<string>();\n if (packageJsonRaw) {\n try {\n const pkg = JSON.parse(packageJsonRaw);\n for (const name of Object.keys(pkg.dependencies ?? {})) {\n directNames.add(name);\n }\n for (const name of Object.keys(pkg.devDependencies ?? {})) {\n directNames.add(name);\n }\n } catch {\n // If package.json can't be parsed, all deps are \"unknown\" direct status\n }\n }\n\n const dependencies = this.parseLockfile(lockfile, directNames);\n\n return {\n projectPath,\n ecosystem: 'npm',\n dependencies,\n lockfilePath,\n scannedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Parses package-lock.json and extracts dependencies.\n * Supports lockfile v2 and v3 (uses the `packages` field).\n * Falls back to `dependencies` field for lockfile v1.\n */\n private parseLockfile(lockfile: NpmLockfile, directNames: Set<string>): Dependency[] {\n const deps: Dependency[] = [];\n\n if (lockfile.packages) {\n // Lockfile v2/v3: `packages` is a flat map of \"node_modules/name\" → info\n for (const [pkgPath, pkgInfo] of Object.entries(lockfile.packages)) {\n // Skip the root package (empty string key)\n if (pkgPath === '') continue;\n\n // Extract package name from the path\n // e.g., \"node_modules/express\" → \"express\"\n // e.g., \"node_modules/@types/node\" → \"@types/node\"\n const name = this.extractPackageName(pkgPath);\n if (!name || !pkgInfo.version) continue;\n\n // Skip link: true entries (workspace references)\n if (pkgInfo.link) continue;\n\n deps.push({\n name,\n version: pkgInfo.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, pkgInfo.version),\n });\n }\n } else if (lockfile.dependencies) {\n // Lockfile v1 fallback: `dependencies` is a nested tree\n this.parseDependenciesV1(lockfile.dependencies, directNames, deps);\n }\n\n return deps;\n }\n\n /**\n * Builds a purl (Package URL) for an npm package.\n *\n * Per the purl spec (https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md):\n * \"The npm scope @ sign prefix is always percent encoded.\"\n *\n * So @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n * And express@4.18.2 → pkg:npm/express@4.18.2\n */\n private buildPurl(name: string, version: string): string {\n if (name.startsWith('@')) {\n // Scoped: encode the @ as %40 per purl spec\n return `pkg:npm/%40${name.slice(1)}@${version}`;\n }\n return `pkg:npm/${name}@${version}`;\n }\n\n /** Extracts the package name from a node_modules path */\n private extractPackageName(pkgPath: string): string | null {\n // \"node_modules/@scope/name\" → \"@scope/name\"\n // \"node_modules/name\" → \"name\"\n // \"node_modules/a/node_modules/b\" → \"b\" (nested)\n const parts = pkgPath.split('node_modules/');\n const last = parts[parts.length - 1];\n return last || null;\n }\n\n /** Recursively parses lockfile v1 `dependencies` tree */\n private parseDependenciesV1(\n depsObj: Record<string, NpmLockfileV1Dep>,\n directNames: Set<string>,\n result: Dependency[]\n ): void {\n for (const [name, info] of Object.entries(depsObj)) {\n if (info.version) {\n result.push({\n name,\n version: info.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, info.version),\n });\n }\n // Recurse into nested dependencies\n if (info.dependencies) {\n this.parseDependenciesV1(info.dependencies, directNames, result);\n }\n }\n }\n}\n\n// ─── Types for package-lock.json parsing ─────────────────────────\n\ninterface NpmLockfile {\n name?: string;\n version?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockfilePackage>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n\ninterface NpmLockfilePackage {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n optional?: boolean;\n link?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfileV1Dep {\n version?: string;\n resolved?: string;\n integrity?: string;\n requires?: Record<string, string>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n","/** Base error for all Verimu errors */\nexport class VerimuError extends Error {\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'VerimuError';\n }\n}\n\n/** Thrown when no supported lockfile is found */\nexport class NoLockfileError extends VerimuError {\n constructor(projectPath: string) {\n super(\n `No supported lockfile found in ${projectPath}. ` +\n `Supported: package-lock.json (npm), packages.lock.json (NuGet), Cargo.lock (Rust)`,\n 'NO_LOCKFILE'\n );\n this.name = 'NoLockfileError';\n }\n}\n\n/** Thrown when lockfile parsing fails */\nexport class LockfileParseError extends VerimuError {\n constructor(lockfilePath: string, reason: string) {\n super(`Failed to parse ${lockfilePath}: ${reason}`, 'LOCKFILE_PARSE_ERROR');\n this.name = 'LockfileParseError';\n }\n}\n\n/** Thrown when a CVE source query fails */\nexport class CveSourceError extends VerimuError {\n constructor(source: string, reason: string) {\n super(`CVE source \"${source}\" failed: ${reason}`, 'CVE_SOURCE_ERROR');\n this.name = 'CveSourceError';\n }\n}\n\n/** Thrown when API key is required but missing */\nexport class ApiKeyRequiredError extends VerimuError {\n constructor(feature: string) {\n super(\n `API key required for \"${feature}\". Get one at https://verimu.com/dashboard`,\n 'API_KEY_REQUIRED'\n );\n this.name = 'ApiKeyRequiredError';\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * C# / NuGet dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - packages.lock.json (NuGet lock file)\n * - *.csproj files (for direct dependency list)\n */\nexport class NugetScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'nuget';\n readonly lockfileNames = ['packages.lock.json'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for packages.lock.json\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('NuGet scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * Rust / Cargo dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - Cargo.lock (resolved dependency tree)\n * - Cargo.toml (for direct dependency list)\n */\nexport class CargoScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'cargo';\n readonly lockfileNames = ['Cargo.lock'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for Cargo.lock\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('Cargo scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from './scanner.interface.js';\nimport type { ScanResult } from '../core/types.js';\nimport { NpmScanner } from './npm/npm-scanner.js';\nimport { NugetScanner } from './nuget/nuget-scanner.js';\nimport { CargoScanner } from './cargo/cargo-scanner.js';\nimport { NoLockfileError } from '../core/errors.js';\n\n/**\n * Registry of all available dependency scanners.\n * Auto-detects the correct scanner for a given project.\n */\nexport class ScannerRegistry {\n private scanners: DependencyScanner[];\n\n constructor() {\n this.scanners = [\n new NpmScanner(),\n new NugetScanner(),\n new CargoScanner(),\n // Add new scanners here as they're implemented\n ];\n }\n\n /**\n * Auto-detects the project's ecosystem and scans dependencies.\n * Tries each registered scanner in order until one matches.\n */\n async detectAndScan(projectPath: string): Promise<ScanResult> {\n for (const scanner of this.scanners) {\n const lockfilePath = await scanner.detect(projectPath);\n if (lockfilePath) {\n return scanner.scan(projectPath, lockfilePath);\n }\n }\n throw new NoLockfileError(projectPath);\n }\n\n /** Returns a specific scanner by ecosystem name */\n getScanner(ecosystem: string): DependencyScanner | undefined {\n return this.scanners.find((s) => s.ecosystem === ecosystem);\n }\n\n /** Lists all registered ecosystems */\n listEcosystems(): string[] {\n return this.scanners.map((s) => s.ecosystem);\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SbomGenerator } from './generator.interface.js';\nimport type { ScanResult, Sbom, SbomFormat, Dependency } from '../core/types.js';\n\n/**\n * Generates CycloneDX 1.7 JSON SBOMs.\n *\n * CycloneDX is the preferred SBOM format for CRA compliance.\n * Spec: https://cyclonedx.org/docs/1.7/json/\n *\n * NTIA minimum elements are satisfied:\n * - metadata.supplier (supplier of the root software)\n * - components[].supplier (supplier of each dependency)\n * - components[].name, version, purl, bom-ref\n * - dependencies[] graph\n */\nexport class CycloneDxGenerator implements SbomGenerator {\n readonly format: SbomFormat = 'cyclonedx-json';\n\n generate(scanResult: ScanResult, toolVersion: string = '0.1.0'): Sbom {\n const bom = this.buildBom(scanResult, toolVersion);\n const content = JSON.stringify(bom, null, 2);\n\n return {\n format: 'cyclonedx-json',\n specVersion: '1.7',\n content,\n componentCount: scanResult.dependencies.length,\n generatedAt: new Date().toISOString(),\n };\n }\n\n private buildBom(scanResult: ScanResult, toolVersion: string): CycloneDxBom {\n const projectName = this.extractProjectName(scanResult.projectPath);\n\n return {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp: new Date().toISOString(),\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: toolVersion,\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n {\n type: 'website',\n url: 'https://verimu.com',\n },\n ],\n },\n ],\n },\n // NTIA: metadata.supplier — the org supplying the root software\n supplier: {\n name: projectName,\n },\n component: {\n type: 'application',\n name: projectName,\n 'bom-ref': 'root-component',\n supplier: { name: projectName },\n },\n },\n components: scanResult.dependencies.map((dep) => this.toComponent(dep)),\n dependencies: this.buildDependencyGraph(scanResult),\n };\n }\n\n /** Converts a Verimu Dependency to a CycloneDX component */\n private toComponent(dep: Dependency): CycloneDxComponent {\n return {\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n // NTIA: component.supplier — derived from npm scope or package name\n supplier: {\n name: this.deriveSupplierName(dep.name),\n },\n };\n }\n\n /**\n * Derives a supplier name from a package name.\n *\n * For scoped packages like \"@vue/reactivity\" → \"@vue\"\n * For unscoped packages like \"express\" → \"express\"\n *\n * This is the same heuristic used by Syft, Trivy, and other SBOM tools\n * when registry metadata (author/publisher) isn't available from the lockfile.\n */\n private deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n // Scoped package: \"@scope/name\" → \"@scope\"\n const scope = packageName.split('/')[0];\n return scope;\n }\n return packageName;\n }\n\n /**\n * Builds the dependency graph section of the SBOM.\n *\n * The root component depends on all dependencies (direct + transitive).\n * This ensures a single root node in the graph, which NTIA validators expect.\n *\n * We include ALL deps under root (not just direct) because from a flat lockfile\n * we can't reliably reconstruct which transitive dep belongs to which direct dep.\n * This is still valid per the CycloneDX spec — it represents a complete but flat\n * dependency relationship.\n */\n private buildDependencyGraph(scanResult: ScanResult): CycloneDxDependencyEntry[] {\n const allDepPurls = scanResult.dependencies.map((d) => d.purl);\n\n return [\n {\n ref: 'root-component',\n dependsOn: allDepPurls,\n },\n ];\n }\n\n /** Extracts project name from path */\n private extractProjectName(projectPath: string): string {\n const parts = projectPath.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1] || 'unknown-project';\n }\n}\n\n// ─── CycloneDX 1.7 JSON Types ──────────────────────────────────\n\ninterface OrganizationalEntity {\n name: string;\n url?: string[];\n contact?: Array<{ name?: string; email?: string; phone?: string }>;\n}\n\ninterface CycloneDxBom {\n $schema: string;\n bomFormat: string;\n specVersion: string;\n serialNumber: string;\n version: number;\n metadata: {\n timestamp: string;\n tools: {\n components: Array<{\n type: string;\n name: string;\n version: string;\n description?: string;\n supplier?: OrganizationalEntity;\n externalReferences?: Array<{ type: string; url: string }>;\n }>;\n };\n supplier: OrganizationalEntity;\n component: {\n type: string;\n name: string;\n 'bom-ref': string;\n supplier: OrganizationalEntity;\n };\n };\n components: CycloneDxComponent[];\n dependencies: CycloneDxDependencyEntry[];\n}\n\ninterface CycloneDxComponent {\n type: string;\n name: string;\n version: string;\n purl: string;\n 'bom-ref': string;\n scope?: string;\n supplier: OrganizationalEntity;\n}\n\ninterface CycloneDxDependencyEntry {\n ref: string;\n dependsOn: string[];\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, Vulnerability, VulnerabilitySource, Severity } from '../core/types.js';\n\nconst OSV_API_BASE = 'https://api.osv.dev/v1';\nconst BATCH_SIZE = 1000; // OSV querybatch supports up to 1000\n\n/**\n * OSV.dev (Google Open Source Vulnerabilities) CVE source.\n *\n * Primary CVE source for Verimu because:\n * - Supports direct package name + ecosystem + version queries\n * - Has batch query endpoint for efficiency\n * - No authentication required\n * - Covers npm, PyPI, Go, Rust, Maven, NuGet, etc.\n * - Aggregates data from GitHub Advisory, NVD, and others\n *\n * API docs: https://google.github.io/osv.dev/api/\n */\nexport class OsvSource implements CveSource {\n readonly sourceId: VulnerabilitySource = 'osv';\n readonly name = 'OSV.dev (Google Open Source Vulnerabilities)';\n\n private fetchFn: typeof fetch;\n\n constructor(fetchImpl?: typeof fetch) {\n // Allow injecting fetch for testing\n this.fetchFn = fetchImpl ?? globalThis.fetch;\n }\n\n async checkDependencies(dependencies: Dependency[]): Promise<Vulnerability[]> {\n if (dependencies.length === 0) return [];\n\n const allVulns: Vulnerability[] = [];\n\n // Process in batches of BATCH_SIZE\n for (let i = 0; i < dependencies.length; i += BATCH_SIZE) {\n const batch = dependencies.slice(i, i + BATCH_SIZE);\n const batchVulns = await this.queryBatch(batch);\n allVulns.push(...batchVulns);\n }\n\n return allVulns;\n }\n\n /** Uses OSV's /querybatch endpoint for efficient bulk lookups */\n private async queryBatch(dependencies: Dependency[]): Promise<Vulnerability[]> {\n const queries = dependencies.map((dep) => ({\n version: dep.version,\n package: {\n name: dep.name,\n ecosystem: this.mapEcosystem(dep.ecosystem),\n },\n }));\n\n const response = await this.fetchFn(`${OSV_API_BASE}/querybatch`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries }),\n });\n\n if (!response.ok) {\n throw new Error(`OSV API error: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n const vulnerabilities: Vulnerability[] = [];\n\n // Each result in `results` corresponds to the query at the same index\n for (let i = 0; i < data.results.length; i++) {\n const result = data.results[i];\n const dep = dependencies[i];\n\n if (result.vulns && result.vulns.length > 0) {\n for (const vuln of result.vulns) {\n vulnerabilities.push(this.mapVulnerability(vuln, dep));\n }\n }\n }\n\n return vulnerabilities;\n }\n\n /** Maps an OSV vulnerability record to our Vulnerability type */\n private mapVulnerability(osvVuln: OsvVulnerability, dep: Dependency): Vulnerability {\n const cveId = this.extractCveId(osvVuln);\n const severity = this.extractSeverity(osvVuln);\n\n return {\n id: cveId || osvVuln.id,\n aliases: Array.from(new Set([osvVuln.id, ...(osvVuln.aliases ?? [])])),\n summary: osvVuln.summary ?? osvVuln.details?.slice(0, 200) ?? 'No description available',\n severity: severity.level,\n cvssScore: severity.score,\n packageName: dep.name,\n ecosystem: dep.ecosystem,\n affectedVersionRange: this.extractAffectedRange(osvVuln, dep.name),\n fixedVersion: this.extractFixedVersion(osvVuln, dep.name),\n exploitedInWild: false, // OSV doesn't track this — CISA KEV does\n source: 'osv',\n referenceUrl: `https://osv.dev/vulnerability/${osvVuln.id}`,\n publishedAt: osvVuln.published,\n };\n }\n\n /** Extracts CVE ID from aliases (prefers CVE-xxxx over GHSA-xxxx) */\n private extractCveId(vuln: OsvVulnerability): string | null {\n // Check the main ID first\n if (vuln.id.startsWith('CVE-')) return vuln.id;\n\n // Check aliases\n if (vuln.aliases) {\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\n if (cve) return cve;\n }\n\n return null;\n }\n\n /** Extracts severity from CVSS scores in the OSV record */\n private extractSeverity(vuln: OsvVulnerability): { level: Severity; score?: number } {\n // Try database_specific first (often has CVSS)\n if (vuln.severity && vuln.severity.length > 0) {\n for (const sev of vuln.severity) {\n if (sev.type === 'CVSS_V3') {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return { level: this.scoreToSeverity(score), score };\n }\n }\n }\n }\n\n // Try to extract from database_specific\n if (vuln.database_specific?.severity) {\n const s = vuln.database_specific.severity.toUpperCase();\n if (['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].includes(s)) {\n return { level: s as Severity };\n }\n }\n\n return { level: 'UNKNOWN' };\n }\n\n /** Parses CVSS v3 vector string to extract the base score */\n private parseCvssScore(vectorOrScore: string): number | null {\n // Could be a raw score like \"7.5\" or a vector like \"CVSS:3.1/AV:N/AC:L/...\"\n const num = parseFloat(vectorOrScore);\n if (!isNaN(num) && num >= 0 && num <= 10) return num;\n\n // If it's a vector string, we'd need to calculate — for now return null\n // and rely on severity text\n return null;\n }\n\n /** Converts a CVSS score (0-10) to a severity level */\n private scoreToSeverity(score: number): Severity {\n if (score >= 9.0) return 'CRITICAL';\n if (score >= 7.0) return 'HIGH';\n if (score >= 4.0) return 'MEDIUM';\n if (score > 0.0) return 'LOW';\n return 'UNKNOWN';\n }\n\n /** Extracts affected version range for a specific package */\n private extractAffectedRange(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const introduced = range.events.find((e) => e.introduced)?.introduced;\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (introduced && fixed) return `>=${introduced}, <${fixed}`;\n if (introduced) return `>=${introduced}`;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Extracts the fixed version for a specific package */\n private extractFixedVersion(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (fixed) return fixed;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Maps our ecosystem names to OSV ecosystem names */\n private mapEcosystem(ecosystem: string): string {\n const map: Record<string, string> = {\n npm: 'npm',\n nuget: 'NuGet',\n cargo: 'crates.io',\n maven: 'Maven',\n pip: 'PyPI',\n go: 'Go',\n };\n return map[ecosystem] ?? ecosystem;\n }\n}\n\n// ─── OSV API Response Types ─────────────────────────────────────\n\ninterface OsvBatchResponse {\n results: Array<{\n vulns?: OsvVulnerability[];\n }>;\n}\n\ninterface OsvVulnerability {\n id: string;\n summary?: string;\n details?: string;\n aliases?: string[];\n published?: string;\n modified?: string;\n severity?: Array<{\n type: string;\n score: string;\n }>;\n affected?: Array<{\n package?: {\n name: string;\n ecosystem: string;\n };\n ranges?: Array<{\n type: string;\n events: Array<{\n introduced?: string;\n fixed?: string;\n last_affected?: string;\n }>;\n }>;\n versions?: string[];\n }>;\n database_specific?: {\n severity?: string;\n [key: string]: unknown;\n };\n references?: Array<{\n type: string;\n url: string;\n }>;\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, CveCheckResult, Vulnerability, VulnerabilitySource } from '../core/types.js';\nimport { OsvSource } from './osv.js';\n\n/**\n * Aggregates vulnerability data from multiple CVE sources.\n * Deduplicates results by CVE ID across sources.\n */\nexport class CveAggregator {\n private sources: CveSource[];\n\n constructor(sources?: CveSource[]) {\n this.sources = sources ?? [\n new OsvSource(),\n // Future: new NvdSource(), new EuvdSource(), new CisaKevSource()\n ];\n }\n\n /**\n * Checks dependencies against all registered CVE sources.\n * Runs sources in parallel and merges/deduplicates results.\n */\n async check(dependencies: Dependency[]): Promise<CveCheckResult> {\n const startTime = Date.now();\n const sourcesQueried: VulnerabilitySource[] = [];\n const sourceErrors: { source: VulnerabilitySource; error: string }[] = [];\n const allVulns: Vulnerability[] = [];\n\n // Run all sources in parallel\n const results = await Promise.allSettled(\n this.sources.map(async (source) => {\n const vulns = await source.checkDependencies(dependencies);\n return { sourceId: source.sourceId, vulns };\n })\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n sourcesQueried.push(result.value.sourceId);\n allVulns.push(...result.value.vulns);\n } else {\n // Extract the source ID from the error context\n const sourceIndex = results.indexOf(result);\n const sourceId = this.sources[sourceIndex].sourceId;\n sourceErrors.push({\n source: sourceId,\n error: result.reason instanceof Error ? result.reason.message : String(result.reason),\n });\n }\n }\n\n // Deduplicate by CVE ID (prefer the entry with more data)\n const deduplicated = this.deduplicateVulnerabilities(allVulns);\n\n return {\n vulnerabilities: deduplicated,\n sourcesQueried,\n sourceErrors,\n checkDurationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Deduplicates vulnerabilities by ID.\n * When the same CVE appears from multiple sources,\n * keeps the one with more complete data (has CVSS score, has fix version, etc.)\n */\n private deduplicateVulnerabilities(vulns: Vulnerability[]): Vulnerability[] {\n const byKey = new Map<string, Vulnerability>();\n\n for (const vuln of vulns) {\n // Key by (vulnerability ID + package name) to handle the same CVE\n // affecting multiple packages\n const key = `${vuln.id}::${vuln.packageName}`;\n const existing = byKey.get(key);\n\n if (!existing) {\n byKey.set(key, vuln);\n } else {\n // Keep the one with more data\n byKey.set(key, this.pickBetterEntry(existing, vuln));\n }\n }\n\n return Array.from(byKey.values());\n }\n\n /** Picks the vulnerability entry with more complete data */\n private pickBetterEntry(a: Vulnerability, b: Vulnerability): Vulnerability {\n let scoreA = 0;\n let scoreB = 0;\n\n if (a.cvssScore !== undefined) scoreA++;\n if (b.cvssScore !== undefined) scoreB++;\n if (a.fixedVersion) scoreA++;\n if (b.fixedVersion) scoreB++;\n if (a.affectedVersionRange) scoreA++;\n if (b.affectedVersionRange) scoreB++;\n if (a.severity !== 'UNKNOWN') scoreA++;\n if (b.severity !== 'UNKNOWN') scoreB++;\n\n // Merge: start with the lesser entry, overlay with the better one.\n // Strip undefined/null values so they don't overwrite real data.\n const strip = (obj: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));\n\n const winner = scoreB > scoreA\n ? { ...strip(a as unknown as Record<string, unknown>), ...strip(b as unknown as Record<string, unknown>) } as unknown as Vulnerability\n : { ...strip(b as unknown as Record<string, unknown>), ...strip(a as unknown as Record<string, unknown>) } as unknown as Vulnerability;\n\n // Merge aliases\n const allAliases = new Set([...a.aliases, ...b.aliases]);\n winner.aliases = Array.from(allAliases);\n\n // If either says exploited, it's exploited\n winner.exploitedInWild = a.exploitedInWild || b.exploitedInWild;\n\n return winner;\n }\n}\n","import type { Reporter } from './reporter.interface.js';\nimport type { VerimuReport, Vulnerability, Severity } from '../core/types.js';\n\n/** Outputs a human-readable console report */\nexport class ConsoleReporter implements Reporter {\n readonly name = 'console';\n\n report(result: VerimuReport): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('┌─────────────────────────────────────────────┐');\n lines.push('│ VERIMU CRA COMPLIANCE SCAN │');\n lines.push('└─────────────────────────────────────────────┘');\n lines.push('');\n\n // Project info\n lines.push(` Project: ${result.project.path}`);\n lines.push(` Ecosystem: ${result.project.ecosystem}`);\n lines.push(` Dependencies: ${result.project.dependencyCount}`);\n lines.push(` Scanned at: ${result.generatedAt}`);\n lines.push('');\n\n // SBOM info\n lines.push(` ✓ SBOM generated (${result.sbom.format}, ${result.sbom.specVersion})`);\n lines.push(` Components: ${result.sbom.componentCount}`);\n lines.push('');\n\n // CVE results\n const vulns = result.cveCheck.vulnerabilities;\n if (vulns.length === 0) {\n lines.push(' ✓ No known vulnerabilities found');\n } else {\n lines.push(` ⚠ ${vulns.length} vulnerabilit${vulns.length === 1 ? 'y' : 'ies'} found:`);\n lines.push('');\n\n // Sort by severity: CRITICAL → HIGH → MEDIUM → LOW → UNKNOWN\n const sorted = [...vulns].sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));\n\n for (const vuln of sorted) {\n const badge = severityBadge(vuln.severity);\n const fix = vuln.fixedVersion ? ` → fix: ${vuln.fixedVersion}` : '';\n lines.push(` ${badge} ${vuln.id}`);\n lines.push(` ${vuln.packageName}@${vuln.affectedVersionRange ?? '?'}${fix}`);\n lines.push(` ${vuln.summary.slice(0, 100)}`);\n if (vuln.exploitedInWild) {\n lines.push(` 🔴 ACTIVELY EXPLOITED — 24h CRA reporting required`);\n }\n lines.push('');\n }\n }\n\n // Sources\n const sources = result.cveCheck.sourcesQueried.join(', ');\n lines.push(` Sources queried: ${sources} (${result.cveCheck.checkDurationMs}ms)`);\n\n if (result.cveCheck.sourceErrors.length > 0) {\n for (const err of result.cveCheck.sourceErrors) {\n lines.push(` ⚠ ${err.source}: ${err.error}`);\n }\n }\n\n // Summary\n lines.push('');\n lines.push(' ─── Summary ───');\n lines.push(` Total: ${result.summary.totalVulnerabilities} | ` +\n `Critical: ${result.summary.critical} | ` +\n `High: ${result.summary.high} | ` +\n `Medium: ${result.summary.medium} | ` +\n `Low: ${result.summary.low}`);\n\n if (result.summary.exploitedInWild > 0) {\n lines.push(` 🔴 ${result.summary.exploitedInWild} actively exploited — immediate action required`);\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n}\n\nfunction severityOrder(s: Severity): number {\n const order: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n return order[s] ?? 5;\n}\n\nfunction severityBadge(s: Severity): string {\n const badges: Record<Severity, string> = {\n CRITICAL: '[CRIT]',\n HIGH: '[HIGH]',\n MEDIUM: '[MED] ',\n LOW: '[LOW] ',\n UNKNOWN: '[???] ',\n };\n return badges[s] ?? '[???] ';\n}\n"],"mappings":"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","names":["import_promises","path","import_crypto","severityOrder"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/generate-sbom.ts","../src/scan.ts","../src/scanners/npm/npm-scanner.ts","../src/core/errors.ts","../src/scanners/nuget/nuget-scanner.ts","../src/scanners/cargo/cargo-scanner.ts","../src/scanners/registry.ts","../src/sbom/cyclonedx.ts","../src/cve/osv.ts","../src/cve/aggregator.ts","../src/reporters/console.ts"],"sourcesContent":["// ─── Primary API ────────────────────────────────────────────────\n//\n// import { scan, generateSbom, shouldFailCi } from 'verimu'\n//\n\nexport { generateSbom } from './generate-sbom.js';\nexport { scan, shouldFailCi, printReport } from './scan.js';\n\n// ─── Types ──────────────────────────────────────────────────────\n\nexport type {\n // generateSbom() types\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n\n // scan() types\n Dependency,\n ScanResult,\n Sbom,\n SbomFormat,\n Severity,\n Vulnerability,\n VulnerabilitySource,\n CveCheckResult,\n VerimuReport,\n VerimuConfig,\n Ecosystem,\n CiProvider,\n} from './core/types.js';\n\n// ─── Errors ─────────────────────────────────────────────────────\n\nexport {\n VerimuError,\n NoLockfileError,\n LockfileParseError,\n CveSourceError,\n ApiKeyRequiredError,\n} from './core/errors.js';\n\n// ─── Advanced / Internal ────────────────────────────────────────\n// For users who need fine-grained control over individual steps.\n\nexport { NpmScanner } from './scanners/npm/npm-scanner.js';\nexport { ScannerRegistry } from './scanners/registry.js';\nexport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nexport { OsvSource } from './cve/osv.js';\nexport { CveAggregator } from './cve/aggregator.js';\nexport { ConsoleReporter } from './reporters/console.js';\n","import { randomUUID } from 'crypto';\nimport type {\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n Ecosystem,\n} from './core/types.js';\n\n/**\n * Generates an NTIA-compliant CycloneDX 1.7 SBOM from structured dependency data.\n *\n * This is a **pure function** — no filesystem access, no network calls, no side effects.\n * It takes a project name, version, and list of dependencies, and returns a complete\n * CycloneDX 1.7 JSON SBOM that passes NTIA minimum-element validation.\n *\n * @example\n * ```ts\n * import { generateSbom } from 'verimu';\n *\n * const result = generateSbom({\n * projectName: 'my-app',\n * projectVersion: '1.0.0',\n * dependencies: [\n * { name: 'express', version: '4.18.2', ecosystem: 'npm' },\n * { name: '@types/node', version: '20.11.5', ecosystem: 'npm', direct: false },\n * ],\n * });\n *\n * console.log(result.componentCount); // 2\n * console.log(result.content); // formatted JSON string\n * ```\n */\nexport function generateSbom(input: GenerateSbomInput): GenerateSbomResult {\n const {\n projectName,\n projectVersion = '0.0.0',\n dependencies,\n } = input;\n\n const timestamp = new Date().toISOString();\n\n // Resolve PURLs for any deps that don't have one\n const resolvedDeps = dependencies.map((dep) => ({\n ...dep,\n direct: dep.direct ?? true,\n purl: dep.purl ?? buildPurl(dep.name, dep.version, dep.ecosystem),\n }));\n\n const rootPurl = buildPurl(projectName, projectVersion, 'npm');\n\n const sbom = {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp,\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: '0.0.1',\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n { type: 'website', url: 'https://verimu.com' },\n ],\n },\n ],\n },\n supplier: { name: projectName },\n component: {\n type: 'application',\n name: projectName,\n version: projectVersion,\n 'bom-ref': rootPurl,\n supplier: { name: projectName },\n },\n },\n components: resolvedDeps.map((dep) => ({\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n supplier: { name: deriveSupplierName(dep.name) },\n })),\n dependencies: [\n {\n ref: rootPurl,\n dependsOn: resolvedDeps.map((d) => d.purl),\n },\n ],\n };\n\n const content = JSON.stringify(sbom, null, 2);\n\n return {\n sbom,\n content,\n componentCount: resolvedDeps.length,\n specVersion: '1.7',\n generatedAt: timestamp,\n };\n}\n\n// ─── Internal helpers ───────────────────────────────────────────\n\nconst PURL_TYPE_MAP: Record<Ecosystem, string> = {\n npm: 'npm',\n nuget: 'nuget',\n cargo: 'cargo',\n maven: 'maven',\n pip: 'pypi',\n go: 'golang',\n};\n\n/**\n * Builds a Package URL (purl) per the purl spec.\n *\n * For npm scoped packages, the @ prefix is percent-encoded as %40:\n * @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n *\n * See: https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md\n */\nfunction buildPurl(name: string, version: string, ecosystem: Ecosystem): string {\n const type = PURL_TYPE_MAP[ecosystem] || ecosystem;\n\n if (ecosystem === 'npm' && name.startsWith('@')) {\n return `pkg:${type}/%40${name.slice(1)}@${version}`;\n }\n\n return `pkg:${type}/${name}@${version}`;\n}\n\n/**\n * Derives supplier name from a package name.\n * Scoped packages: \"@vue/reactivity\" → \"@vue\"\n * Unscoped packages: \"express\" → \"express\"\n */\nfunction deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n return packageName.split('/')[0];\n }\n return packageName;\n}\n","import { writeFile } from 'fs/promises';\nimport { ScannerRegistry } from './scanners/registry.js';\nimport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nimport { CveAggregator } from './cve/aggregator.js';\nimport { ConsoleReporter } from './reporters/console.js';\nimport type { VerimuConfig, VerimuReport, Severity } from './core/types.js';\n\n/**\n * Main scan pipeline — orchestrates the full Verimu workflow:\n * 1. Detect ecosystem & parse lockfile\n * 2. Generate CycloneDX SBOM\n * 3. Check dependencies for CVEs\n * 4. Produce report\n * 5. Optionally upload snapshot to Verimu API\n */\nexport async function scan(config: VerimuConfig): Promise<VerimuReport> {\n const {\n projectPath,\n sbomOutput = './sbom.cdx.json',\n skipCveCheck = false,\n } = config;\n\n // 1. Scan dependencies\n const registry = new ScannerRegistry();\n const scanResult = await registry.detectAndScan(projectPath);\n\n // 2. Generate SBOM\n const sbomGenerator = new CycloneDxGenerator();\n const sbom = sbomGenerator.generate(scanResult);\n\n // 3. Write SBOM to disk\n await writeFile(sbomOutput, sbom.content, 'utf-8');\n\n // 4. Check CVEs (unless skipped)\n let cveCheck;\n if (skipCveCheck) {\n cveCheck = {\n vulnerabilities: [],\n sourcesQueried: [],\n sourceErrors: [],\n checkDurationMs: 0,\n };\n } else {\n const aggregator = new CveAggregator();\n cveCheck = await aggregator.check(scanResult.dependencies);\n }\n\n // 5. Build report\n const summary = {\n totalDependencies: scanResult.dependencies.length,\n totalVulnerabilities: cveCheck.vulnerabilities.length,\n critical: cveCheck.vulnerabilities.filter((v) => v.severity === 'CRITICAL').length,\n high: cveCheck.vulnerabilities.filter((v) => v.severity === 'HIGH').length,\n medium: cveCheck.vulnerabilities.filter((v) => v.severity === 'MEDIUM').length,\n low: cveCheck.vulnerabilities.filter((v) => v.severity === 'LOW').length,\n exploitedInWild: cveCheck.vulnerabilities.filter((v) => v.exploitedInWild).length,\n };\n\n const report: VerimuReport = {\n project: {\n path: projectPath,\n ecosystem: scanResult.ecosystem,\n dependencyCount: scanResult.dependencies.length,\n },\n sbom,\n cveCheck,\n summary,\n generatedAt: new Date().toISOString(),\n };\n\n return report;\n}\n\n/**\n * Determines if the scan should fail CI based on severity threshold.\n */\nexport function shouldFailCi(report: VerimuReport, threshold: Severity): boolean {\n const severityOrder: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n const thresholdLevel = severityOrder[threshold] ?? 4;\n\n return report.cveCheck.vulnerabilities.some(\n (v) => severityOrder[v.severity] <= thresholdLevel\n );\n}\n\n/**\n * Prints a console report to stdout.\n */\nexport function printReport(report: VerimuReport): void {\n const reporter = new ConsoleReporter();\n console.log(reporter.report(report));\n}\n","import { readFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport type { DependencyScanner } from '../scanner.interface.js';\nimport type { Dependency, Ecosystem, ScanResult } from '../../core/types.js';\nimport { LockfileParseError } from '../../core/errors.js';\n\n/**\n * npm / Node.js dependency scanner.\n *\n * Parses package-lock.json (v2/v3 format) to extract the full\n * resolved dependency tree. Also reads package.json to determine\n * which dependencies are direct vs transitive.\n */\nexport class NpmScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'npm';\n readonly lockfileNames = ['package-lock.json'];\n\n async detect(projectPath: string): Promise<string | null> {\n const lockfilePath = path.join(projectPath, 'package-lock.json');\n return existsSync(lockfilePath) ? lockfilePath : null;\n }\n\n async scan(projectPath: string, lockfilePath: string): Promise<ScanResult> {\n const [lockfileRaw, packageJsonRaw] = await Promise.all([\n readFile(lockfilePath, 'utf-8'),\n readFile(path.join(projectPath, 'package.json'), 'utf-8').catch(() => null),\n ]);\n\n let lockfile: NpmLockfile;\n try {\n lockfile = JSON.parse(lockfileRaw);\n } catch {\n throw new LockfileParseError(lockfilePath, 'Invalid JSON');\n }\n\n // Determine direct dependency names from package.json\n const directNames = new Set<string>();\n if (packageJsonRaw) {\n try {\n const pkg = JSON.parse(packageJsonRaw);\n for (const name of Object.keys(pkg.dependencies ?? {})) {\n directNames.add(name);\n }\n for (const name of Object.keys(pkg.devDependencies ?? {})) {\n directNames.add(name);\n }\n } catch {\n // If package.json can't be parsed, all deps are \"unknown\" direct status\n }\n }\n\n const dependencies = this.parseLockfile(lockfile, directNames);\n\n return {\n projectPath,\n ecosystem: 'npm',\n dependencies,\n lockfilePath,\n scannedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Parses package-lock.json and extracts dependencies.\n * Supports lockfile v2 and v3 (uses the `packages` field).\n * Falls back to `dependencies` field for lockfile v1.\n */\n private parseLockfile(lockfile: NpmLockfile, directNames: Set<string>): Dependency[] {\n const deps: Dependency[] = [];\n\n if (lockfile.packages) {\n // Lockfile v2/v3: `packages` is a flat map of \"node_modules/name\" → info\n for (const [pkgPath, pkgInfo] of Object.entries(lockfile.packages)) {\n // Skip the root package (empty string key)\n if (pkgPath === '') continue;\n\n // Extract package name from the path\n // e.g., \"node_modules/express\" → \"express\"\n // e.g., \"node_modules/@types/node\" → \"@types/node\"\n const name = this.extractPackageName(pkgPath);\n if (!name || !pkgInfo.version) continue;\n\n // Skip link: true entries (workspace references)\n if (pkgInfo.link) continue;\n\n deps.push({\n name,\n version: pkgInfo.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, pkgInfo.version),\n });\n }\n } else if (lockfile.dependencies) {\n // Lockfile v1 fallback: `dependencies` is a nested tree\n this.parseDependenciesV1(lockfile.dependencies, directNames, deps);\n }\n\n return deps;\n }\n\n /**\n * Builds a purl (Package URL) for an npm package.\n *\n * Per the purl spec (https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md):\n * \"The npm scope @ sign prefix is always percent encoded.\"\n *\n * So @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n * And express@4.18.2 → pkg:npm/express@4.18.2\n */\n private buildPurl(name: string, version: string): string {\n if (name.startsWith('@')) {\n // Scoped: encode the @ as %40 per purl spec\n return `pkg:npm/%40${name.slice(1)}@${version}`;\n }\n return `pkg:npm/${name}@${version}`;\n }\n\n /** Extracts the package name from a node_modules path */\n private extractPackageName(pkgPath: string): string | null {\n // \"node_modules/@scope/name\" → \"@scope/name\"\n // \"node_modules/name\" → \"name\"\n // \"node_modules/a/node_modules/b\" → \"b\" (nested)\n const parts = pkgPath.split('node_modules/');\n const last = parts[parts.length - 1];\n return last || null;\n }\n\n /** Recursively parses lockfile v1 `dependencies` tree */\n private parseDependenciesV1(\n depsObj: Record<string, NpmLockfileV1Dep>,\n directNames: Set<string>,\n result: Dependency[]\n ): void {\n for (const [name, info] of Object.entries(depsObj)) {\n if (info.version) {\n result.push({\n name,\n version: info.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, info.version),\n });\n }\n // Recurse into nested dependencies\n if (info.dependencies) {\n this.parseDependenciesV1(info.dependencies, directNames, result);\n }\n }\n }\n}\n\n// ─── Types for package-lock.json parsing ─────────────────────────\n\ninterface NpmLockfile {\n name?: string;\n version?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockfilePackage>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n\ninterface NpmLockfilePackage {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n optional?: boolean;\n link?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfileV1Dep {\n version?: string;\n resolved?: string;\n integrity?: string;\n requires?: Record<string, string>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n","/** Base error for all Verimu errors */\nexport class VerimuError extends Error {\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'VerimuError';\n }\n}\n\n/** Thrown when no supported lockfile is found */\nexport class NoLockfileError extends VerimuError {\n constructor(projectPath: string) {\n super(\n `No supported lockfile found in ${projectPath}. ` +\n `Supported: package-lock.json (npm), packages.lock.json (NuGet), Cargo.lock (Rust)`,\n 'NO_LOCKFILE'\n );\n this.name = 'NoLockfileError';\n }\n}\n\n/** Thrown when lockfile parsing fails */\nexport class LockfileParseError extends VerimuError {\n constructor(lockfilePath: string, reason: string) {\n super(`Failed to parse ${lockfilePath}: ${reason}`, 'LOCKFILE_PARSE_ERROR');\n this.name = 'LockfileParseError';\n }\n}\n\n/** Thrown when a CVE source query fails */\nexport class CveSourceError extends VerimuError {\n constructor(source: string, reason: string) {\n super(`CVE source \"${source}\" failed: ${reason}`, 'CVE_SOURCE_ERROR');\n this.name = 'CveSourceError';\n }\n}\n\n/** Thrown when API key is required but missing */\nexport class ApiKeyRequiredError extends VerimuError {\n constructor(feature: string) {\n super(\n `API key required for \"${feature}\". Get one at https://verimu.com/dashboard`,\n 'API_KEY_REQUIRED'\n );\n this.name = 'ApiKeyRequiredError';\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * C# / NuGet dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - packages.lock.json (NuGet lock file)\n * - *.csproj files (for direct dependency list)\n */\nexport class NugetScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'nuget';\n readonly lockfileNames = ['packages.lock.json'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for packages.lock.json\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('NuGet scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * Rust / Cargo dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - Cargo.lock (resolved dependency tree)\n * - Cargo.toml (for direct dependency list)\n */\nexport class CargoScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'cargo';\n readonly lockfileNames = ['Cargo.lock'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for Cargo.lock\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('Cargo scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from './scanner.interface.js';\nimport type { ScanResult } from '../core/types.js';\nimport { NpmScanner } from './npm/npm-scanner.js';\nimport { NugetScanner } from './nuget/nuget-scanner.js';\nimport { CargoScanner } from './cargo/cargo-scanner.js';\nimport { NoLockfileError } from '../core/errors.js';\n\n/**\n * Registry of all available dependency scanners.\n * Auto-detects the correct scanner for a given project.\n */\nexport class ScannerRegistry {\n private scanners: DependencyScanner[];\n\n constructor() {\n this.scanners = [\n new NpmScanner(),\n new NugetScanner(),\n new CargoScanner(),\n // Add new scanners here as they're implemented\n ];\n }\n\n /**\n * Auto-detects the project's ecosystem and scans dependencies.\n * Tries each registered scanner in order until one matches.\n */\n async detectAndScan(projectPath: string): Promise<ScanResult> {\n for (const scanner of this.scanners) {\n const lockfilePath = await scanner.detect(projectPath);\n if (lockfilePath) {\n return scanner.scan(projectPath, lockfilePath);\n }\n }\n throw new NoLockfileError(projectPath);\n }\n\n /** Returns a specific scanner by ecosystem name */\n getScanner(ecosystem: string): DependencyScanner | undefined {\n return this.scanners.find((s) => s.ecosystem === ecosystem);\n }\n\n /** Lists all registered ecosystems */\n listEcosystems(): string[] {\n return this.scanners.map((s) => s.ecosystem);\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SbomGenerator } from './generator.interface.js';\nimport type { ScanResult, Sbom, SbomFormat, Dependency } from '../core/types.js';\n\n/**\n * Generates CycloneDX 1.7 JSON SBOMs.\n *\n * CycloneDX is the preferred SBOM format for CRA compliance.\n * Spec: https://cyclonedx.org/docs/1.7/json/\n *\n * NTIA minimum elements are satisfied:\n * - metadata.supplier (supplier of the root software)\n * - components[].supplier (supplier of each dependency)\n * - components[].name, version, purl, bom-ref\n * - dependencies[] graph\n */\nexport class CycloneDxGenerator implements SbomGenerator {\n readonly format: SbomFormat = 'cyclonedx-json';\n\n generate(scanResult: ScanResult, toolVersion: string = '0.1.0'): Sbom {\n const bom = this.buildBom(scanResult, toolVersion);\n const content = JSON.stringify(bom, null, 2);\n\n return {\n format: 'cyclonedx-json',\n specVersion: '1.7',\n content,\n componentCount: scanResult.dependencies.length,\n generatedAt: new Date().toISOString(),\n };\n }\n\n private buildBom(scanResult: ScanResult, toolVersion: string): CycloneDxBom {\n const projectName = this.extractProjectName(scanResult.projectPath);\n\n return {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp: new Date().toISOString(),\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: toolVersion,\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n {\n type: 'website',\n url: 'https://verimu.com',\n },\n ],\n },\n ],\n },\n // NTIA: metadata.supplier — the org supplying the root software\n supplier: {\n name: projectName,\n },\n component: {\n type: 'application',\n name: projectName,\n 'bom-ref': 'root-component',\n supplier: { name: projectName },\n },\n },\n components: scanResult.dependencies.map((dep) => this.toComponent(dep)),\n dependencies: this.buildDependencyGraph(scanResult),\n };\n }\n\n /** Converts a Verimu Dependency to a CycloneDX component */\n private toComponent(dep: Dependency): CycloneDxComponent {\n return {\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n // NTIA: component.supplier — derived from npm scope or package name\n supplier: {\n name: this.deriveSupplierName(dep.name),\n },\n };\n }\n\n /**\n * Derives a supplier name from a package name.\n *\n * For scoped packages like \"@vue/reactivity\" → \"@vue\"\n * For unscoped packages like \"express\" → \"express\"\n *\n * This is the same heuristic used by Syft, Trivy, and other SBOM tools\n * when registry metadata (author/publisher) isn't available from the lockfile.\n */\n private deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n // Scoped package: \"@scope/name\" → \"@scope\"\n const scope = packageName.split('/')[0];\n return scope;\n }\n return packageName;\n }\n\n /**\n * Builds the dependency graph section of the SBOM.\n *\n * The root component depends on all dependencies (direct + transitive).\n * This ensures a single root node in the graph, which NTIA validators expect.\n *\n * We include ALL deps under root (not just direct) because from a flat lockfile\n * we can't reliably reconstruct which transitive dep belongs to which direct dep.\n * This is still valid per the CycloneDX spec — it represents a complete but flat\n * dependency relationship.\n */\n private buildDependencyGraph(scanResult: ScanResult): CycloneDxDependencyEntry[] {\n const allDepPurls = scanResult.dependencies.map((d) => d.purl);\n\n return [\n {\n ref: 'root-component',\n dependsOn: allDepPurls,\n },\n ];\n }\n\n /** Extracts project name from path */\n private extractProjectName(projectPath: string): string {\n const parts = projectPath.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1] || 'unknown-project';\n }\n}\n\n// ─── CycloneDX 1.7 JSON Types ──────────────────────────────────\n\ninterface OrganizationalEntity {\n name: string;\n url?: string[];\n contact?: Array<{ name?: string; email?: string; phone?: string }>;\n}\n\ninterface CycloneDxBom {\n $schema: string;\n bomFormat: string;\n specVersion: string;\n serialNumber: string;\n version: number;\n metadata: {\n timestamp: string;\n tools: {\n components: Array<{\n type: string;\n name: string;\n version: string;\n description?: string;\n supplier?: OrganizationalEntity;\n externalReferences?: Array<{ type: string; url: string }>;\n }>;\n };\n supplier: OrganizationalEntity;\n component: {\n type: string;\n name: string;\n 'bom-ref': string;\n supplier: OrganizationalEntity;\n };\n };\n components: CycloneDxComponent[];\n dependencies: CycloneDxDependencyEntry[];\n}\n\ninterface CycloneDxComponent {\n type: string;\n name: string;\n version: string;\n purl: string;\n 'bom-ref': string;\n scope?: string;\n supplier: OrganizationalEntity;\n}\n\ninterface CycloneDxDependencyEntry {\n ref: string;\n dependsOn: string[];\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, Vulnerability, VulnerabilitySource, Severity } from '../core/types.js';\n\nconst OSV_API_BASE = 'https://api.osv.dev/v1';\nconst BATCH_SIZE = 1000; // OSV querybatch supports up to 1000\n\n/**\n * OSV.dev (Google Open Source Vulnerabilities) CVE source.\n *\n * Primary CVE source for Verimu because:\n * - Supports direct package name + ecosystem + version queries\n * - Has batch query endpoint for efficiency\n * - No authentication required\n * - Covers npm, PyPI, Go, Rust, Maven, NuGet, etc.\n * - Aggregates data from GitHub Advisory, NVD, and others\n *\n * API docs: https://google.github.io/osv.dev/api/\n */\nexport class OsvSource implements CveSource {\n readonly sourceId: VulnerabilitySource = 'osv';\n readonly name = 'OSV.dev (Google Open Source Vulnerabilities)';\n\n private fetchFn: typeof fetch;\n\n constructor(fetchImpl?: typeof fetch) {\n // Allow injecting fetch for testing\n this.fetchFn = fetchImpl ?? globalThis.fetch;\n }\n\n async checkDependencies(dependencies: Dependency[]): Promise<Vulnerability[]> {\n if (dependencies.length === 0) return [];\n\n const allVulns: Vulnerability[] = [];\n\n // Process in batches of BATCH_SIZE\n for (let i = 0; i < dependencies.length; i += BATCH_SIZE) {\n const batch = dependencies.slice(i, i + BATCH_SIZE);\n const batchVulns = await this.queryBatch(batch);\n allVulns.push(...batchVulns);\n }\n\n return allVulns;\n }\n\n /** Uses OSV's /querybatch endpoint for efficient bulk lookups */\n private async queryBatch(dependencies: Dependency[]): Promise<Vulnerability[]> {\n const queries = dependencies.map((dep) => ({\n version: dep.version,\n package: {\n name: dep.name,\n ecosystem: this.mapEcosystem(dep.ecosystem),\n },\n }));\n\n const response = await this.fetchFn(`${OSV_API_BASE}/querybatch`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries }),\n });\n\n if (!response.ok) {\n throw new Error(`OSV API error: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n const vulnerabilities: Vulnerability[] = [];\n\n // Each result in `results` corresponds to the query at the same index\n for (let i = 0; i < data.results.length; i++) {\n const result = data.results[i];\n const dep = dependencies[i];\n\n if (result.vulns && result.vulns.length > 0) {\n for (const vuln of result.vulns) {\n vulnerabilities.push(this.mapVulnerability(vuln, dep));\n }\n }\n }\n\n return vulnerabilities;\n }\n\n /** Maps an OSV vulnerability record to our Vulnerability type */\n private mapVulnerability(osvVuln: OsvVulnerability, dep: Dependency): Vulnerability {\n const cveId = this.extractCveId(osvVuln);\n const severity = this.extractSeverity(osvVuln);\n\n return {\n id: cveId || osvVuln.id,\n aliases: Array.from(new Set([osvVuln.id, ...(osvVuln.aliases ?? [])])),\n summary: osvVuln.summary ?? osvVuln.details?.slice(0, 200) ?? 'No description available',\n severity: severity.level,\n cvssScore: severity.score,\n packageName: dep.name,\n ecosystem: dep.ecosystem,\n affectedVersionRange: this.extractAffectedRange(osvVuln, dep.name),\n fixedVersion: this.extractFixedVersion(osvVuln, dep.name),\n exploitedInWild: false, // OSV doesn't track this — CISA KEV does\n source: 'osv',\n referenceUrl: `https://osv.dev/vulnerability/${osvVuln.id}`,\n publishedAt: osvVuln.published,\n };\n }\n\n /** Extracts CVE ID from aliases (prefers CVE-xxxx over GHSA-xxxx) */\n private extractCveId(vuln: OsvVulnerability): string | null {\n // Check the main ID first\n if (vuln.id.startsWith('CVE-')) return vuln.id;\n\n // Check aliases\n if (vuln.aliases) {\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\n if (cve) return cve;\n }\n\n return null;\n }\n\n /** Extracts severity from CVSS scores in the OSV record */\n private extractSeverity(vuln: OsvVulnerability): { level: Severity; score?: number } {\n // Try database_specific first (often has CVSS)\n if (vuln.severity && vuln.severity.length > 0) {\n for (const sev of vuln.severity) {\n if (sev.type === 'CVSS_V3') {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return { level: this.scoreToSeverity(score), score };\n }\n }\n }\n }\n\n // Try to extract from database_specific\n if (vuln.database_specific?.severity) {\n const s = vuln.database_specific.severity.toUpperCase();\n if (['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].includes(s)) {\n return { level: s as Severity };\n }\n }\n\n return { level: 'UNKNOWN' };\n }\n\n /** Parses CVSS v3 vector string to extract the base score */\n private parseCvssScore(vectorOrScore: string): number | null {\n // Could be a raw score like \"7.5\" or a vector like \"CVSS:3.1/AV:N/AC:L/...\"\n const num = parseFloat(vectorOrScore);\n if (!isNaN(num) && num >= 0 && num <= 10) return num;\n\n // If it's a vector string, we'd need to calculate — for now return null\n // and rely on severity text\n return null;\n }\n\n /** Converts a CVSS score (0-10) to a severity level */\n private scoreToSeverity(score: number): Severity {\n if (score >= 9.0) return 'CRITICAL';\n if (score >= 7.0) return 'HIGH';\n if (score >= 4.0) return 'MEDIUM';\n if (score > 0.0) return 'LOW';\n return 'UNKNOWN';\n }\n\n /** Extracts affected version range for a specific package */\n private extractAffectedRange(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const introduced = range.events.find((e) => e.introduced)?.introduced;\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (introduced && fixed) return `>=${introduced}, <${fixed}`;\n if (introduced) return `>=${introduced}`;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Extracts the fixed version for a specific package */\n private extractFixedVersion(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (fixed) return fixed;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Maps our ecosystem names to OSV ecosystem names */\n private mapEcosystem(ecosystem: string): string {\n const map: Record<string, string> = {\n npm: 'npm',\n nuget: 'NuGet',\n cargo: 'crates.io',\n maven: 'Maven',\n pip: 'PyPI',\n go: 'Go',\n };\n return map[ecosystem] ?? ecosystem;\n }\n}\n\n// ─── OSV API Response Types ─────────────────────────────────────\n\ninterface OsvBatchResponse {\n results: Array<{\n vulns?: OsvVulnerability[];\n }>;\n}\n\ninterface OsvVulnerability {\n id: string;\n summary?: string;\n details?: string;\n aliases?: string[];\n published?: string;\n modified?: string;\n severity?: Array<{\n type: string;\n score: string;\n }>;\n affected?: Array<{\n package?: {\n name: string;\n ecosystem: string;\n };\n ranges?: Array<{\n type: string;\n events: Array<{\n introduced?: string;\n fixed?: string;\n last_affected?: string;\n }>;\n }>;\n versions?: string[];\n }>;\n database_specific?: {\n severity?: string;\n [key: string]: unknown;\n };\n references?: Array<{\n type: string;\n url: string;\n }>;\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, CveCheckResult, Vulnerability, VulnerabilitySource } from '../core/types.js';\nimport { OsvSource } from './osv.js';\n\n/**\n * Aggregates vulnerability data from multiple CVE sources.\n * Deduplicates results by CVE ID across sources.\n */\nexport class CveAggregator {\n private sources: CveSource[];\n\n constructor(sources?: CveSource[]) {\n this.sources = sources ?? [\n new OsvSource(),\n // Future: new NvdSource(), new EuvdSource(), new CisaKevSource()\n ];\n }\n\n /**\n * Checks dependencies against all registered CVE sources.\n * Runs sources in parallel and merges/deduplicates results.\n */\n async check(dependencies: Dependency[]): Promise<CveCheckResult> {\n const startTime = Date.now();\n const sourcesQueried: VulnerabilitySource[] = [];\n const sourceErrors: { source: VulnerabilitySource; error: string }[] = [];\n const allVulns: Vulnerability[] = [];\n\n // Run all sources in parallel\n const results = await Promise.allSettled(\n this.sources.map(async (source) => {\n const vulns = await source.checkDependencies(dependencies);\n return { sourceId: source.sourceId, vulns };\n })\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n sourcesQueried.push(result.value.sourceId);\n allVulns.push(...result.value.vulns);\n } else {\n // Extract the source ID from the error context\n const sourceIndex = results.indexOf(result);\n const sourceId = this.sources[sourceIndex].sourceId;\n sourceErrors.push({\n source: sourceId,\n error: result.reason instanceof Error ? result.reason.message : String(result.reason),\n });\n }\n }\n\n // Deduplicate by CVE ID (prefer the entry with more data)\n const deduplicated = this.deduplicateVulnerabilities(allVulns);\n\n return {\n vulnerabilities: deduplicated,\n sourcesQueried,\n sourceErrors,\n checkDurationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Deduplicates vulnerabilities by ID.\n * When the same CVE appears from multiple sources,\n * keeps the one with more complete data (has CVSS score, has fix version, etc.)\n */\n private deduplicateVulnerabilities(vulns: Vulnerability[]): Vulnerability[] {\n const byKey = new Map<string, Vulnerability>();\n\n for (const vuln of vulns) {\n // Key by (vulnerability ID + package name) to handle the same CVE\n // affecting multiple packages\n const key = `${vuln.id}::${vuln.packageName}`;\n const existing = byKey.get(key);\n\n if (!existing) {\n byKey.set(key, vuln);\n } else {\n // Keep the one with more data\n byKey.set(key, this.pickBetterEntry(existing, vuln));\n }\n }\n\n return Array.from(byKey.values());\n }\n\n /** Picks the vulnerability entry with more complete data */\n private pickBetterEntry(a: Vulnerability, b: Vulnerability): Vulnerability {\n let scoreA = 0;\n let scoreB = 0;\n\n if (a.cvssScore !== undefined) scoreA++;\n if (b.cvssScore !== undefined) scoreB++;\n if (a.fixedVersion) scoreA++;\n if (b.fixedVersion) scoreB++;\n if (a.affectedVersionRange) scoreA++;\n if (b.affectedVersionRange) scoreB++;\n if (a.severity !== 'UNKNOWN') scoreA++;\n if (b.severity !== 'UNKNOWN') scoreB++;\n\n // Merge: start with the lesser entry, overlay with the better one.\n // Strip undefined/null values so they don't overwrite real data.\n const strip = (obj: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));\n\n const winner = scoreB > scoreA\n ? { ...strip(a as unknown as Record<string, unknown>), ...strip(b as unknown as Record<string, unknown>) } as unknown as Vulnerability\n : { ...strip(b as unknown as Record<string, unknown>), ...strip(a as unknown as Record<string, unknown>) } as unknown as Vulnerability;\n\n // Merge aliases\n const allAliases = new Set([...a.aliases, ...b.aliases]);\n winner.aliases = Array.from(allAliases);\n\n // If either says exploited, it's exploited\n winner.exploitedInWild = a.exploitedInWild || b.exploitedInWild;\n\n return winner;\n }\n}\n","import type { Reporter } from './reporter.interface.js';\nimport type { VerimuReport, Vulnerability, Severity } from '../core/types.js';\n\n/** Outputs a human-readable console report */\nexport class ConsoleReporter implements Reporter {\n readonly name = 'console';\n\n report(result: VerimuReport): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('┌─────────────────────────────────────────────┐');\n lines.push('│ VERIMU CRA COMPLIANCE SCAN │');\n lines.push('└─────────────────────────────────────────────┘');\n lines.push('');\n\n // Project info\n lines.push(` Project: ${result.project.path}`);\n lines.push(` Ecosystem: ${result.project.ecosystem}`);\n lines.push(` Dependencies: ${result.project.dependencyCount}`);\n lines.push(` Scanned at: ${result.generatedAt}`);\n lines.push('');\n\n // SBOM info\n lines.push(` ✓ SBOM generated (${result.sbom.format}, ${result.sbom.specVersion})`);\n lines.push(` Components: ${result.sbom.componentCount}`);\n lines.push('');\n\n // CVE results\n const vulns = result.cveCheck.vulnerabilities;\n if (vulns.length === 0) {\n lines.push(' ✓ No known vulnerabilities found');\n } else {\n lines.push(` ⚠ ${vulns.length} vulnerabilit${vulns.length === 1 ? 'y' : 'ies'} found:`);\n lines.push('');\n\n // Sort by severity: CRITICAL → HIGH → MEDIUM → LOW → UNKNOWN\n const sorted = [...vulns].sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));\n\n for (const vuln of sorted) {\n const badge = severityBadge(vuln.severity);\n const fix = vuln.fixedVersion ? ` → fix: ${vuln.fixedVersion}` : '';\n lines.push(` ${badge} ${vuln.id}`);\n lines.push(` ${vuln.packageName}@${vuln.affectedVersionRange ?? '?'}${fix}`);\n lines.push(` ${vuln.summary.slice(0, 100)}`);\n if (vuln.exploitedInWild) {\n lines.push(` 🔴 ACTIVELY EXPLOITED — 24h CRA reporting required`);\n }\n lines.push('');\n }\n }\n\n // Sources\n const sources = result.cveCheck.sourcesQueried.join(', ');\n lines.push(` Sources queried: ${sources} (${result.cveCheck.checkDurationMs}ms)`);\n\n if (result.cveCheck.sourceErrors.length > 0) {\n for (const err of result.cveCheck.sourceErrors) {\n lines.push(` ⚠ ${err.source}: ${err.error}`);\n }\n }\n\n // Summary\n lines.push('');\n lines.push(' ─── Summary ───');\n lines.push(` Total: ${result.summary.totalVulnerabilities} | ` +\n `Critical: ${result.summary.critical} | ` +\n `High: ${result.summary.high} | ` +\n `Medium: ${result.summary.medium} | ` +\n `Low: ${result.summary.low}`);\n\n if (result.summary.exploitedInWild > 0) {\n lines.push(` 🔴 ${result.summary.exploitedInWild} actively exploited — immediate action required`);\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n}\n\nfunction severityOrder(s: Severity): number {\n const order: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n return order[s] ?? 5;\n}\n\nfunction severityBadge(s: Severity): string {\n const badges: Record<Severity, string> = {\n CRITICAL: '[CRIT]',\n HIGH: '[HIGH]',\n MEDIUM: '[MED] ',\n LOW: '[LOW] ',\n UNKNOWN: '[???] ',\n };\n return badges[s] ?? '[???] ';\n}\n"],"mappings":"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","names":["import_promises","path","import_crypto","severityOrder"]}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate-sbom.ts","../src/scan.ts","../src/scanners/npm/npm-scanner.ts","../src/core/errors.ts","../src/scanners/nuget/nuget-scanner.ts","../src/scanners/cargo/cargo-scanner.ts","../src/scanners/registry.ts","../src/sbom/cyclonedx.ts","../src/cve/osv.ts","../src/cve/aggregator.ts","../src/reporters/console.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport type {\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n Ecosystem,\n} from './core/types.js';\n\n/**\n * Generates an NTIA-compliant CycloneDX 1.7 SBOM from structured dependency data.\n *\n * This is a **pure function** — no filesystem access, no network calls, no side effects.\n * It takes a project name, version, and list of dependencies, and returns a complete\n * CycloneDX 1.7 JSON SBOM that passes NTIA minimum-element validation.\n *\n * @example\n * ```ts\n * import { generateSbom } from 'verimu';\n *\n * const result = generateSbom({\n * projectName: 'my-app',\n * projectVersion: '1.0.0',\n * dependencies: [\n * { name: 'express', version: '4.18.2', ecosystem: 'npm' },\n * { name: '@types/node', version: '20.11.5', ecosystem: 'npm', direct: false },\n * ],\n * });\n *\n * console.log(result.componentCount); // 2\n * console.log(result.content); // formatted JSON string\n * ```\n */\nexport function generateSbom(input: GenerateSbomInput): GenerateSbomResult {\n const {\n projectName,\n projectVersion = '0.0.0',\n dependencies,\n } = input;\n\n const timestamp = new Date().toISOString();\n\n // Resolve PURLs for any deps that don't have one\n const resolvedDeps = dependencies.map((dep) => ({\n ...dep,\n direct: dep.direct ?? true,\n purl: dep.purl ?? buildPurl(dep.name, dep.version, dep.ecosystem),\n }));\n\n const rootPurl = buildPurl(projectName, projectVersion, 'npm');\n\n const sbom = {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp,\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: '0.0.1',\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n { type: 'website', url: 'https://verimu.eu' },\n ],\n },\n ],\n },\n supplier: { name: projectName },\n component: {\n type: 'application',\n name: projectName,\n version: projectVersion,\n 'bom-ref': rootPurl,\n supplier: { name: projectName },\n },\n },\n components: resolvedDeps.map((dep) => ({\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n supplier: { name: deriveSupplierName(dep.name) },\n })),\n dependencies: [\n {\n ref: rootPurl,\n dependsOn: resolvedDeps.map((d) => d.purl),\n },\n ],\n };\n\n const content = JSON.stringify(sbom, null, 2);\n\n return {\n sbom,\n content,\n componentCount: resolvedDeps.length,\n specVersion: '1.7',\n generatedAt: timestamp,\n };\n}\n\n// ─── Internal helpers ───────────────────────────────────────────\n\nconst PURL_TYPE_MAP: Record<Ecosystem, string> = {\n npm: 'npm',\n nuget: 'nuget',\n cargo: 'cargo',\n maven: 'maven',\n pip: 'pypi',\n go: 'golang',\n};\n\n/**\n * Builds a Package URL (purl) per the purl spec.\n *\n * For npm scoped packages, the @ prefix is percent-encoded as %40:\n * @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n *\n * See: https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md\n */\nfunction buildPurl(name: string, version: string, ecosystem: Ecosystem): string {\n const type = PURL_TYPE_MAP[ecosystem] || ecosystem;\n\n if (ecosystem === 'npm' && name.startsWith('@')) {\n return `pkg:${type}/%40${name.slice(1)}@${version}`;\n }\n\n return `pkg:${type}/${name}@${version}`;\n}\n\n/**\n * Derives supplier name from a package name.\n * Scoped packages: \"@vue/reactivity\" → \"@vue\"\n * Unscoped packages: \"express\" → \"express\"\n */\nfunction deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n return packageName.split('/')[0];\n }\n return packageName;\n}\n","import { writeFile } from 'fs/promises';\nimport { ScannerRegistry } from './scanners/registry.js';\nimport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nimport { CveAggregator } from './cve/aggregator.js';\nimport { ConsoleReporter } from './reporters/console.js';\nimport type { VerimuConfig, VerimuReport, Severity } from './core/types.js';\n\n/**\n * Main scan pipeline — orchestrates the full Verimu workflow:\n * 1. Detect ecosystem & parse lockfile\n * 2. Generate CycloneDX SBOM\n * 3. Check dependencies for CVEs\n * 4. Produce report\n * 5. Optionally upload snapshot to Verimu API\n */\nexport async function scan(config: VerimuConfig): Promise<VerimuReport> {\n const {\n projectPath,\n sbomOutput = './sbom.cdx.json',\n skipCveCheck = false,\n } = config;\n\n // 1. Scan dependencies\n const registry = new ScannerRegistry();\n const scanResult = await registry.detectAndScan(projectPath);\n\n // 2. Generate SBOM\n const sbomGenerator = new CycloneDxGenerator();\n const sbom = sbomGenerator.generate(scanResult);\n\n // 3. Write SBOM to disk\n await writeFile(sbomOutput, sbom.content, 'utf-8');\n\n // 4. Check CVEs (unless skipped)\n let cveCheck;\n if (skipCveCheck) {\n cveCheck = {\n vulnerabilities: [],\n sourcesQueried: [],\n sourceErrors: [],\n checkDurationMs: 0,\n };\n } else {\n const aggregator = new CveAggregator();\n cveCheck = await aggregator.check(scanResult.dependencies);\n }\n\n // 5. Build report\n const summary = {\n totalDependencies: scanResult.dependencies.length,\n totalVulnerabilities: cveCheck.vulnerabilities.length,\n critical: cveCheck.vulnerabilities.filter((v) => v.severity === 'CRITICAL').length,\n high: cveCheck.vulnerabilities.filter((v) => v.severity === 'HIGH').length,\n medium: cveCheck.vulnerabilities.filter((v) => v.severity === 'MEDIUM').length,\n low: cveCheck.vulnerabilities.filter((v) => v.severity === 'LOW').length,\n exploitedInWild: cveCheck.vulnerabilities.filter((v) => v.exploitedInWild).length,\n };\n\n const report: VerimuReport = {\n project: {\n path: projectPath,\n ecosystem: scanResult.ecosystem,\n dependencyCount: scanResult.dependencies.length,\n },\n sbom,\n cveCheck,\n summary,\n generatedAt: new Date().toISOString(),\n };\n\n return report;\n}\n\n/**\n * Determines if the scan should fail CI based on severity threshold.\n */\nexport function shouldFailCi(report: VerimuReport, threshold: Severity): boolean {\n const severityOrder: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n const thresholdLevel = severityOrder[threshold] ?? 4;\n\n return report.cveCheck.vulnerabilities.some(\n (v) => severityOrder[v.severity] <= thresholdLevel\n );\n}\n\n/**\n * Prints a console report to stdout.\n */\nexport function printReport(report: VerimuReport): void {\n const reporter = new ConsoleReporter();\n console.log(reporter.report(report));\n}\n","import { readFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport type { DependencyScanner } from '../scanner.interface.js';\nimport type { Dependency, Ecosystem, ScanResult } from '../../core/types.js';\nimport { LockfileParseError } from '../../core/errors.js';\n\n/**\n * npm / Node.js dependency scanner.\n *\n * Parses package-lock.json (v2/v3 format) to extract the full\n * resolved dependency tree. Also reads package.json to determine\n * which dependencies are direct vs transitive.\n */\nexport class NpmScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'npm';\n readonly lockfileNames = ['package-lock.json'];\n\n async detect(projectPath: string): Promise<string | null> {\n const lockfilePath = path.join(projectPath, 'package-lock.json');\n return existsSync(lockfilePath) ? lockfilePath : null;\n }\n\n async scan(projectPath: string, lockfilePath: string): Promise<ScanResult> {\n const [lockfileRaw, packageJsonRaw] = await Promise.all([\n readFile(lockfilePath, 'utf-8'),\n readFile(path.join(projectPath, 'package.json'), 'utf-8').catch(() => null),\n ]);\n\n let lockfile: NpmLockfile;\n try {\n lockfile = JSON.parse(lockfileRaw);\n } catch {\n throw new LockfileParseError(lockfilePath, 'Invalid JSON');\n }\n\n // Determine direct dependency names from package.json\n const directNames = new Set<string>();\n if (packageJsonRaw) {\n try {\n const pkg = JSON.parse(packageJsonRaw);\n for (const name of Object.keys(pkg.dependencies ?? {})) {\n directNames.add(name);\n }\n for (const name of Object.keys(pkg.devDependencies ?? {})) {\n directNames.add(name);\n }\n } catch {\n // If package.json can't be parsed, all deps are \"unknown\" direct status\n }\n }\n\n const dependencies = this.parseLockfile(lockfile, directNames);\n\n return {\n projectPath,\n ecosystem: 'npm',\n dependencies,\n lockfilePath,\n scannedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Parses package-lock.json and extracts dependencies.\n * Supports lockfile v2 and v3 (uses the `packages` field).\n * Falls back to `dependencies` field for lockfile v1.\n */\n private parseLockfile(lockfile: NpmLockfile, directNames: Set<string>): Dependency[] {\n const deps: Dependency[] = [];\n\n if (lockfile.packages) {\n // Lockfile v2/v3: `packages` is a flat map of \"node_modules/name\" → info\n for (const [pkgPath, pkgInfo] of Object.entries(lockfile.packages)) {\n // Skip the root package (empty string key)\n if (pkgPath === '') continue;\n\n // Extract package name from the path\n // e.g., \"node_modules/express\" → \"express\"\n // e.g., \"node_modules/@types/node\" → \"@types/node\"\n const name = this.extractPackageName(pkgPath);\n if (!name || !pkgInfo.version) continue;\n\n // Skip link: true entries (workspace references)\n if (pkgInfo.link) continue;\n\n deps.push({\n name,\n version: pkgInfo.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, pkgInfo.version),\n });\n }\n } else if (lockfile.dependencies) {\n // Lockfile v1 fallback: `dependencies` is a nested tree\n this.parseDependenciesV1(lockfile.dependencies, directNames, deps);\n }\n\n return deps;\n }\n\n /**\n * Builds a purl (Package URL) for an npm package.\n *\n * Per the purl spec (https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md):\n * \"The npm scope @ sign prefix is always percent encoded.\"\n *\n * So @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n * And express@4.18.2 → pkg:npm/express@4.18.2\n */\n private buildPurl(name: string, version: string): string {\n if (name.startsWith('@')) {\n // Scoped: encode the @ as %40 per purl spec\n return `pkg:npm/%40${name.slice(1)}@${version}`;\n }\n return `pkg:npm/${name}@${version}`;\n }\n\n /** Extracts the package name from a node_modules path */\n private extractPackageName(pkgPath: string): string | null {\n // \"node_modules/@scope/name\" → \"@scope/name\"\n // \"node_modules/name\" → \"name\"\n // \"node_modules/a/node_modules/b\" → \"b\" (nested)\n const parts = pkgPath.split('node_modules/');\n const last = parts[parts.length - 1];\n return last || null;\n }\n\n /** Recursively parses lockfile v1 `dependencies` tree */\n private parseDependenciesV1(\n depsObj: Record<string, NpmLockfileV1Dep>,\n directNames: Set<string>,\n result: Dependency[]\n ): void {\n for (const [name, info] of Object.entries(depsObj)) {\n if (info.version) {\n result.push({\n name,\n version: info.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, info.version),\n });\n }\n // Recurse into nested dependencies\n if (info.dependencies) {\n this.parseDependenciesV1(info.dependencies, directNames, result);\n }\n }\n }\n}\n\n// ─── Types for package-lock.json parsing ─────────────────────────\n\ninterface NpmLockfile {\n name?: string;\n version?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockfilePackage>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n\ninterface NpmLockfilePackage {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n optional?: boolean;\n link?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfileV1Dep {\n version?: string;\n resolved?: string;\n integrity?: string;\n requires?: Record<string, string>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n","/** Base error for all Verimu errors */\nexport class VerimuError extends Error {\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'VerimuError';\n }\n}\n\n/** Thrown when no supported lockfile is found */\nexport class NoLockfileError extends VerimuError {\n constructor(projectPath: string) {\n super(\n `No supported lockfile found in ${projectPath}. ` +\n `Supported: package-lock.json (npm), packages.lock.json (NuGet), Cargo.lock (Rust)`,\n 'NO_LOCKFILE'\n );\n this.name = 'NoLockfileError';\n }\n}\n\n/** Thrown when lockfile parsing fails */\nexport class LockfileParseError extends VerimuError {\n constructor(lockfilePath: string, reason: string) {\n super(`Failed to parse ${lockfilePath}: ${reason}`, 'LOCKFILE_PARSE_ERROR');\n this.name = 'LockfileParseError';\n }\n}\n\n/** Thrown when a CVE source query fails */\nexport class CveSourceError extends VerimuError {\n constructor(source: string, reason: string) {\n super(`CVE source \"${source}\" failed: ${reason}`, 'CVE_SOURCE_ERROR');\n this.name = 'CveSourceError';\n }\n}\n\n/** Thrown when API key is required but missing */\nexport class ApiKeyRequiredError extends VerimuError {\n constructor(feature: string) {\n super(\n `API key required for \"${feature}\". Get one at https://verimu.com/dashboard`,\n 'API_KEY_REQUIRED'\n );\n this.name = 'ApiKeyRequiredError';\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * C# / NuGet dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - packages.lock.json (NuGet lock file)\n * - *.csproj files (for direct dependency list)\n */\nexport class NugetScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'nuget';\n readonly lockfileNames = ['packages.lock.json'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for packages.lock.json\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('NuGet scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * Rust / Cargo dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - Cargo.lock (resolved dependency tree)\n * - Cargo.toml (for direct dependency list)\n */\nexport class CargoScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'cargo';\n readonly lockfileNames = ['Cargo.lock'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for Cargo.lock\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('Cargo scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from './scanner.interface.js';\nimport type { ScanResult } from '../core/types.js';\nimport { NpmScanner } from './npm/npm-scanner.js';\nimport { NugetScanner } from './nuget/nuget-scanner.js';\nimport { CargoScanner } from './cargo/cargo-scanner.js';\nimport { NoLockfileError } from '../core/errors.js';\n\n/**\n * Registry of all available dependency scanners.\n * Auto-detects the correct scanner for a given project.\n */\nexport class ScannerRegistry {\n private scanners: DependencyScanner[];\n\n constructor() {\n this.scanners = [\n new NpmScanner(),\n new NugetScanner(),\n new CargoScanner(),\n // Add new scanners here as they're implemented\n ];\n }\n\n /**\n * Auto-detects the project's ecosystem and scans dependencies.\n * Tries each registered scanner in order until one matches.\n */\n async detectAndScan(projectPath: string): Promise<ScanResult> {\n for (const scanner of this.scanners) {\n const lockfilePath = await scanner.detect(projectPath);\n if (lockfilePath) {\n return scanner.scan(projectPath, lockfilePath);\n }\n }\n throw new NoLockfileError(projectPath);\n }\n\n /** Returns a specific scanner by ecosystem name */\n getScanner(ecosystem: string): DependencyScanner | undefined {\n return this.scanners.find((s) => s.ecosystem === ecosystem);\n }\n\n /** Lists all registered ecosystems */\n listEcosystems(): string[] {\n return this.scanners.map((s) => s.ecosystem);\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SbomGenerator } from './generator.interface.js';\nimport type { ScanResult, Sbom, SbomFormat, Dependency } from '../core/types.js';\n\n/**\n * Generates CycloneDX 1.7 JSON SBOMs.\n *\n * CycloneDX is the preferred SBOM format for CRA compliance.\n * Spec: https://cyclonedx.org/docs/1.7/json/\n *\n * NTIA minimum elements are satisfied:\n * - metadata.supplier (supplier of the root software)\n * - components[].supplier (supplier of each dependency)\n * - components[].name, version, purl, bom-ref\n * - dependencies[] graph\n */\nexport class CycloneDxGenerator implements SbomGenerator {\n readonly format: SbomFormat = 'cyclonedx-json';\n\n generate(scanResult: ScanResult, toolVersion: string = '0.1.0'): Sbom {\n const bom = this.buildBom(scanResult, toolVersion);\n const content = JSON.stringify(bom, null, 2);\n\n return {\n format: 'cyclonedx-json',\n specVersion: '1.7',\n content,\n componentCount: scanResult.dependencies.length,\n generatedAt: new Date().toISOString(),\n };\n }\n\n private buildBom(scanResult: ScanResult, toolVersion: string): CycloneDxBom {\n const projectName = this.extractProjectName(scanResult.projectPath);\n\n return {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp: new Date().toISOString(),\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: toolVersion,\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n {\n type: 'website',\n url: 'https://verimu.com',\n },\n ],\n },\n ],\n },\n // NTIA: metadata.supplier — the org supplying the root software\n supplier: {\n name: projectName,\n },\n component: {\n type: 'application',\n name: projectName,\n 'bom-ref': 'root-component',\n supplier: { name: projectName },\n },\n },\n components: scanResult.dependencies.map((dep) => this.toComponent(dep)),\n dependencies: this.buildDependencyGraph(scanResult),\n };\n }\n\n /** Converts a Verimu Dependency to a CycloneDX component */\n private toComponent(dep: Dependency): CycloneDxComponent {\n return {\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n // NTIA: component.supplier — derived from npm scope or package name\n supplier: {\n name: this.deriveSupplierName(dep.name),\n },\n };\n }\n\n /**\n * Derives a supplier name from a package name.\n *\n * For scoped packages like \"@vue/reactivity\" → \"@vue\"\n * For unscoped packages like \"express\" → \"express\"\n *\n * This is the same heuristic used by Syft, Trivy, and other SBOM tools\n * when registry metadata (author/publisher) isn't available from the lockfile.\n */\n private deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n // Scoped package: \"@scope/name\" → \"@scope\"\n const scope = packageName.split('/')[0];\n return scope;\n }\n return packageName;\n }\n\n /**\n * Builds the dependency graph section of the SBOM.\n *\n * The root component depends on all dependencies (direct + transitive).\n * This ensures a single root node in the graph, which NTIA validators expect.\n *\n * We include ALL deps under root (not just direct) because from a flat lockfile\n * we can't reliably reconstruct which transitive dep belongs to which direct dep.\n * This is still valid per the CycloneDX spec — it represents a complete but flat\n * dependency relationship.\n */\n private buildDependencyGraph(scanResult: ScanResult): CycloneDxDependencyEntry[] {\n const allDepPurls = scanResult.dependencies.map((d) => d.purl);\n\n return [\n {\n ref: 'root-component',\n dependsOn: allDepPurls,\n },\n ];\n }\n\n /** Extracts project name from path */\n private extractProjectName(projectPath: string): string {\n const parts = projectPath.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1] || 'unknown-project';\n }\n}\n\n// ─── CycloneDX 1.7 JSON Types ──────────────────────────────────\n\ninterface OrganizationalEntity {\n name: string;\n url?: string[];\n contact?: Array<{ name?: string; email?: string; phone?: string }>;\n}\n\ninterface CycloneDxBom {\n $schema: string;\n bomFormat: string;\n specVersion: string;\n serialNumber: string;\n version: number;\n metadata: {\n timestamp: string;\n tools: {\n components: Array<{\n type: string;\n name: string;\n version: string;\n description?: string;\n supplier?: OrganizationalEntity;\n externalReferences?: Array<{ type: string; url: string }>;\n }>;\n };\n supplier: OrganizationalEntity;\n component: {\n type: string;\n name: string;\n 'bom-ref': string;\n supplier: OrganizationalEntity;\n };\n };\n components: CycloneDxComponent[];\n dependencies: CycloneDxDependencyEntry[];\n}\n\ninterface CycloneDxComponent {\n type: string;\n name: string;\n version: string;\n purl: string;\n 'bom-ref': string;\n scope?: string;\n supplier: OrganizationalEntity;\n}\n\ninterface CycloneDxDependencyEntry {\n ref: string;\n dependsOn: string[];\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, Vulnerability, VulnerabilitySource, Severity } from '../core/types.js';\n\nconst OSV_API_BASE = 'https://api.osv.dev/v1';\nconst BATCH_SIZE = 1000; // OSV querybatch supports up to 1000\n\n/**\n * OSV.dev (Google Open Source Vulnerabilities) CVE source.\n *\n * Primary CVE source for Verimu because:\n * - Supports direct package name + ecosystem + version queries\n * - Has batch query endpoint for efficiency\n * - No authentication required\n * - Covers npm, PyPI, Go, Rust, Maven, NuGet, etc.\n * - Aggregates data from GitHub Advisory, NVD, and others\n *\n * API docs: https://google.github.io/osv.dev/api/\n */\nexport class OsvSource implements CveSource {\n readonly sourceId: VulnerabilitySource = 'osv';\n readonly name = 'OSV.dev (Google Open Source Vulnerabilities)';\n\n private fetchFn: typeof fetch;\n\n constructor(fetchImpl?: typeof fetch) {\n // Allow injecting fetch for testing\n this.fetchFn = fetchImpl ?? globalThis.fetch;\n }\n\n async checkDependencies(dependencies: Dependency[]): Promise<Vulnerability[]> {\n if (dependencies.length === 0) return [];\n\n const allVulns: Vulnerability[] = [];\n\n // Process in batches of BATCH_SIZE\n for (let i = 0; i < dependencies.length; i += BATCH_SIZE) {\n const batch = dependencies.slice(i, i + BATCH_SIZE);\n const batchVulns = await this.queryBatch(batch);\n allVulns.push(...batchVulns);\n }\n\n return allVulns;\n }\n\n /** Uses OSV's /querybatch endpoint for efficient bulk lookups */\n private async queryBatch(dependencies: Dependency[]): Promise<Vulnerability[]> {\n const queries = dependencies.map((dep) => ({\n version: dep.version,\n package: {\n name: dep.name,\n ecosystem: this.mapEcosystem(dep.ecosystem),\n },\n }));\n\n const response = await this.fetchFn(`${OSV_API_BASE}/querybatch`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries }),\n });\n\n if (!response.ok) {\n throw new Error(`OSV API error: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n const vulnerabilities: Vulnerability[] = [];\n\n // Each result in `results` corresponds to the query at the same index\n for (let i = 0; i < data.results.length; i++) {\n const result = data.results[i];\n const dep = dependencies[i];\n\n if (result.vulns && result.vulns.length > 0) {\n for (const vuln of result.vulns) {\n vulnerabilities.push(this.mapVulnerability(vuln, dep));\n }\n }\n }\n\n return vulnerabilities;\n }\n\n /** Maps an OSV vulnerability record to our Vulnerability type */\n private mapVulnerability(osvVuln: OsvVulnerability, dep: Dependency): Vulnerability {\n const cveId = this.extractCveId(osvVuln);\n const severity = this.extractSeverity(osvVuln);\n\n return {\n id: cveId || osvVuln.id,\n aliases: Array.from(new Set([osvVuln.id, ...(osvVuln.aliases ?? [])])),\n summary: osvVuln.summary ?? osvVuln.details?.slice(0, 200) ?? 'No description available',\n severity: severity.level,\n cvssScore: severity.score,\n packageName: dep.name,\n ecosystem: dep.ecosystem,\n affectedVersionRange: this.extractAffectedRange(osvVuln, dep.name),\n fixedVersion: this.extractFixedVersion(osvVuln, dep.name),\n exploitedInWild: false, // OSV doesn't track this — CISA KEV does\n source: 'osv',\n referenceUrl: `https://osv.dev/vulnerability/${osvVuln.id}`,\n publishedAt: osvVuln.published,\n };\n }\n\n /** Extracts CVE ID from aliases (prefers CVE-xxxx over GHSA-xxxx) */\n private extractCveId(vuln: OsvVulnerability): string | null {\n // Check the main ID first\n if (vuln.id.startsWith('CVE-')) return vuln.id;\n\n // Check aliases\n if (vuln.aliases) {\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\n if (cve) return cve;\n }\n\n return null;\n }\n\n /** Extracts severity from CVSS scores in the OSV record */\n private extractSeverity(vuln: OsvVulnerability): { level: Severity; score?: number } {\n // Try database_specific first (often has CVSS)\n if (vuln.severity && vuln.severity.length > 0) {\n for (const sev of vuln.severity) {\n if (sev.type === 'CVSS_V3') {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return { level: this.scoreToSeverity(score), score };\n }\n }\n }\n }\n\n // Try to extract from database_specific\n if (vuln.database_specific?.severity) {\n const s = vuln.database_specific.severity.toUpperCase();\n if (['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].includes(s)) {\n return { level: s as Severity };\n }\n }\n\n return { level: 'UNKNOWN' };\n }\n\n /** Parses CVSS v3 vector string to extract the base score */\n private parseCvssScore(vectorOrScore: string): number | null {\n // Could be a raw score like \"7.5\" or a vector like \"CVSS:3.1/AV:N/AC:L/...\"\n const num = parseFloat(vectorOrScore);\n if (!isNaN(num) && num >= 0 && num <= 10) return num;\n\n // If it's a vector string, we'd need to calculate — for now return null\n // and rely on severity text\n return null;\n }\n\n /** Converts a CVSS score (0-10) to a severity level */\n private scoreToSeverity(score: number): Severity {\n if (score >= 9.0) return 'CRITICAL';\n if (score >= 7.0) return 'HIGH';\n if (score >= 4.0) return 'MEDIUM';\n if (score > 0.0) return 'LOW';\n return 'UNKNOWN';\n }\n\n /** Extracts affected version range for a specific package */\n private extractAffectedRange(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const introduced = range.events.find((e) => e.introduced)?.introduced;\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (introduced && fixed) return `>=${introduced}, <${fixed}`;\n if (introduced) return `>=${introduced}`;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Extracts the fixed version for a specific package */\n private extractFixedVersion(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (fixed) return fixed;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Maps our ecosystem names to OSV ecosystem names */\n private mapEcosystem(ecosystem: string): string {\n const map: Record<string, string> = {\n npm: 'npm',\n nuget: 'NuGet',\n cargo: 'crates.io',\n maven: 'Maven',\n pip: 'PyPI',\n go: 'Go',\n };\n return map[ecosystem] ?? ecosystem;\n }\n}\n\n// ─── OSV API Response Types ─────────────────────────────────────\n\ninterface OsvBatchResponse {\n results: Array<{\n vulns?: OsvVulnerability[];\n }>;\n}\n\ninterface OsvVulnerability {\n id: string;\n summary?: string;\n details?: string;\n aliases?: string[];\n published?: string;\n modified?: string;\n severity?: Array<{\n type: string;\n score: string;\n }>;\n affected?: Array<{\n package?: {\n name: string;\n ecosystem: string;\n };\n ranges?: Array<{\n type: string;\n events: Array<{\n introduced?: string;\n fixed?: string;\n last_affected?: string;\n }>;\n }>;\n versions?: string[];\n }>;\n database_specific?: {\n severity?: string;\n [key: string]: unknown;\n };\n references?: Array<{\n type: string;\n url: string;\n }>;\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, CveCheckResult, Vulnerability, VulnerabilitySource } from '../core/types.js';\nimport { OsvSource } from './osv.js';\n\n/**\n * Aggregates vulnerability data from multiple CVE sources.\n * Deduplicates results by CVE ID across sources.\n */\nexport class CveAggregator {\n private sources: CveSource[];\n\n constructor(sources?: CveSource[]) {\n this.sources = sources ?? [\n new OsvSource(),\n // Future: new NvdSource(), new EuvdSource(), new CisaKevSource()\n ];\n }\n\n /**\n * Checks dependencies against all registered CVE sources.\n * Runs sources in parallel and merges/deduplicates results.\n */\n async check(dependencies: Dependency[]): Promise<CveCheckResult> {\n const startTime = Date.now();\n const sourcesQueried: VulnerabilitySource[] = [];\n const sourceErrors: { source: VulnerabilitySource; error: string }[] = [];\n const allVulns: Vulnerability[] = [];\n\n // Run all sources in parallel\n const results = await Promise.allSettled(\n this.sources.map(async (source) => {\n const vulns = await source.checkDependencies(dependencies);\n return { sourceId: source.sourceId, vulns };\n })\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n sourcesQueried.push(result.value.sourceId);\n allVulns.push(...result.value.vulns);\n } else {\n // Extract the source ID from the error context\n const sourceIndex = results.indexOf(result);\n const sourceId = this.sources[sourceIndex].sourceId;\n sourceErrors.push({\n source: sourceId,\n error: result.reason instanceof Error ? result.reason.message : String(result.reason),\n });\n }\n }\n\n // Deduplicate by CVE ID (prefer the entry with more data)\n const deduplicated = this.deduplicateVulnerabilities(allVulns);\n\n return {\n vulnerabilities: deduplicated,\n sourcesQueried,\n sourceErrors,\n checkDurationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Deduplicates vulnerabilities by ID.\n * When the same CVE appears from multiple sources,\n * keeps the one with more complete data (has CVSS score, has fix version, etc.)\n */\n private deduplicateVulnerabilities(vulns: Vulnerability[]): Vulnerability[] {\n const byKey = new Map<string, Vulnerability>();\n\n for (const vuln of vulns) {\n // Key by (vulnerability ID + package name) to handle the same CVE\n // affecting multiple packages\n const key = `${vuln.id}::${vuln.packageName}`;\n const existing = byKey.get(key);\n\n if (!existing) {\n byKey.set(key, vuln);\n } else {\n // Keep the one with more data\n byKey.set(key, this.pickBetterEntry(existing, vuln));\n }\n }\n\n return Array.from(byKey.values());\n }\n\n /** Picks the vulnerability entry with more complete data */\n private pickBetterEntry(a: Vulnerability, b: Vulnerability): Vulnerability {\n let scoreA = 0;\n let scoreB = 0;\n\n if (a.cvssScore !== undefined) scoreA++;\n if (b.cvssScore !== undefined) scoreB++;\n if (a.fixedVersion) scoreA++;\n if (b.fixedVersion) scoreB++;\n if (a.affectedVersionRange) scoreA++;\n if (b.affectedVersionRange) scoreB++;\n if (a.severity !== 'UNKNOWN') scoreA++;\n if (b.severity !== 'UNKNOWN') scoreB++;\n\n // Merge: start with the lesser entry, overlay with the better one.\n // Strip undefined/null values so they don't overwrite real data.\n const strip = (obj: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));\n\n const winner = scoreB > scoreA\n ? { ...strip(a as unknown as Record<string, unknown>), ...strip(b as unknown as Record<string, unknown>) } as unknown as Vulnerability\n : { ...strip(b as unknown as Record<string, unknown>), ...strip(a as unknown as Record<string, unknown>) } as unknown as Vulnerability;\n\n // Merge aliases\n const allAliases = new Set([...a.aliases, ...b.aliases]);\n winner.aliases = Array.from(allAliases);\n\n // If either says exploited, it's exploited\n winner.exploitedInWild = a.exploitedInWild || b.exploitedInWild;\n\n return winner;\n }\n}\n","import type { Reporter } from './reporter.interface.js';\nimport type { VerimuReport, Vulnerability, Severity } from '../core/types.js';\n\n/** Outputs a human-readable console report */\nexport class ConsoleReporter implements Reporter {\n readonly name = 'console';\n\n report(result: VerimuReport): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('┌─────────────────────────────────────────────┐');\n lines.push('│ VERIMU CRA COMPLIANCE SCAN │');\n lines.push('└─────────────────────────────────────────────┘');\n lines.push('');\n\n // Project info\n lines.push(` Project: ${result.project.path}`);\n lines.push(` Ecosystem: ${result.project.ecosystem}`);\n lines.push(` Dependencies: ${result.project.dependencyCount}`);\n lines.push(` Scanned at: ${result.generatedAt}`);\n lines.push('');\n\n // SBOM info\n lines.push(` ✓ SBOM generated (${result.sbom.format}, ${result.sbom.specVersion})`);\n lines.push(` Components: ${result.sbom.componentCount}`);\n lines.push('');\n\n // CVE results\n const vulns = result.cveCheck.vulnerabilities;\n if (vulns.length === 0) {\n lines.push(' ✓ No known vulnerabilities found');\n } else {\n lines.push(` ⚠ ${vulns.length} vulnerabilit${vulns.length === 1 ? 'y' : 'ies'} found:`);\n lines.push('');\n\n // Sort by severity: CRITICAL → HIGH → MEDIUM → LOW → UNKNOWN\n const sorted = [...vulns].sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));\n\n for (const vuln of sorted) {\n const badge = severityBadge(vuln.severity);\n const fix = vuln.fixedVersion ? ` → fix: ${vuln.fixedVersion}` : '';\n lines.push(` ${badge} ${vuln.id}`);\n lines.push(` ${vuln.packageName}@${vuln.affectedVersionRange ?? '?'}${fix}`);\n lines.push(` ${vuln.summary.slice(0, 100)}`);\n if (vuln.exploitedInWild) {\n lines.push(` 🔴 ACTIVELY EXPLOITED — 24h CRA reporting required`);\n }\n lines.push('');\n }\n }\n\n // Sources\n const sources = result.cveCheck.sourcesQueried.join(', ');\n lines.push(` Sources queried: ${sources} (${result.cveCheck.checkDurationMs}ms)`);\n\n if (result.cveCheck.sourceErrors.length > 0) {\n for (const err of result.cveCheck.sourceErrors) {\n lines.push(` ⚠ ${err.source}: ${err.error}`);\n }\n }\n\n // Summary\n lines.push('');\n lines.push(' ─── Summary ───');\n lines.push(` Total: ${result.summary.totalVulnerabilities} | ` +\n `Critical: ${result.summary.critical} | ` +\n `High: ${result.summary.high} | ` +\n `Medium: ${result.summary.medium} | ` +\n `Low: ${result.summary.low}`);\n\n if (result.summary.exploitedInWild > 0) {\n lines.push(` 🔴 ${result.summary.exploitedInWild} actively exploited — immediate action required`);\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n}\n\nfunction severityOrder(s: Severity): number {\n const order: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n return order[s] ?? 5;\n}\n\nfunction severityBadge(s: Severity): string {\n const badges: Record<Severity, string> = {\n CRITICAL: '[CRIT]',\n HIGH: '[HIGH]',\n MEDIUM: '[MED] ',\n LOW: '[LOW] ',\n UNKNOWN: '[???] ',\n };\n return badges[s] ?? '[???] ';\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAgCpB,SAAS,aAAa,OAA8C;AACzE,QAAM;AAAA,IACJ;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,EACF,IAAI;AAEJ,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAGzC,QAAM,eAAe,aAAa,IAAI,CAAC,SAAS;AAAA,IAC9C,GAAG;AAAA,IACH,QAAQ,IAAI,UAAU;AAAA,IACtB,MAAM,IAAI,QAAQ,UAAU,IAAI,MAAM,IAAI,SAAS,IAAI,SAAS;AAAA,EAClE,EAAE;AAEF,QAAM,WAAW,UAAU,aAAa,gBAAgB,KAAK;AAE7D,QAAM,OAAO;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc,YAAY,WAAW,CAAC;AAAA,IACtC,SAAS;AAAA,IACT,UAAU;AAAA,MACR;AAAA,MACA,OAAO;AAAA,QACL,YAAY;AAAA,UACV;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,SAAS;AAAA,YACT,aAAa;AAAA,YACb,UAAU,EAAE,MAAM,SAAS;AAAA,YAC3B,oBAAoB;AAAA,cAClB,EAAE,MAAM,WAAW,KAAK,oBAAoB;AAAA,YAC9C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,UAAU,EAAE,MAAM,YAAY;AAAA,MAC9B,WAAW;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,QACX,UAAU,EAAE,MAAM,YAAY;AAAA,MAChC;AAAA,IACF;AAAA,IACA,YAAY,aAAa,IAAI,CAAC,SAAS;AAAA,MACrC,MAAM;AAAA,MACN,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,OAAO,IAAI,SAAS,aAAa;AAAA,MACjC,UAAU,EAAE,MAAM,mBAAmB,IAAI,IAAI,EAAE;AAAA,IACjD,EAAE;AAAA,IACF,cAAc;AAAA,MACZ;AAAA,QACE,KAAK;AAAA,QACL,WAAW,aAAa,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC;AAE5C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,gBAAgB,aAAa;AAAA,IAC7B,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AACF;AAIA,IAAM,gBAA2C;AAAA,EAC/C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,IAAI;AACN;AAUA,SAAS,UAAU,MAAc,SAAiB,WAA8B;AAC9E,QAAM,OAAO,cAAc,SAAS,KAAK;AAEzC,MAAI,cAAc,SAAS,KAAK,WAAW,GAAG,GAAG;AAC/C,WAAO,OAAO,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO;AAAA,EACnD;AAEA,SAAO,OAAO,IAAI,IAAI,IAAI,IAAI,OAAO;AACvC;AAOA,SAAS,mBAAmB,aAA6B;AACvD,MAAI,YAAY,WAAW,GAAG,GAAG;AAC/B,WAAO,YAAY,MAAM,GAAG,EAAE,CAAC;AAAA,EACjC;AACA,SAAO;AACT;;;ACpJA,SAAS,iBAAiB;;;ACA1B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,OAAO,UAAU;;;ACDV,IAAM,cAAN,cAA0B,MAAM;AAAA,EACrC,YAAY,SAAiC,MAAc;AACzD,UAAM,OAAO;AAD8B;AAE3C,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,kBAAN,cAA8B,YAAY;AAAA,EAC/C,YAAY,aAAqB;AAC/B;AAAA,MACE,kCAAkC,WAAW;AAAA,MAE7C;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,qBAAN,cAAiC,YAAY;AAAA,EAClD,YAAY,cAAsB,QAAgB;AAChD,UAAM,mBAAmB,YAAY,KAAK,MAAM,IAAI,sBAAsB;AAC1E,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,iBAAN,cAA6B,YAAY;AAAA,EAC9C,YAAY,QAAgB,QAAgB;AAC1C,UAAM,eAAe,MAAM,aAAa,MAAM,IAAI,kBAAkB;AACpE,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,sBAAN,cAAkC,YAAY;AAAA,EACnD,YAAY,SAAiB;AAC3B;AAAA,MACE,yBAAyB,OAAO;AAAA,MAChC;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;;;AD/BO,IAAM,aAAN,MAA8C;AAAA,EAC1C,YAAuB;AAAA,EACvB,gBAAgB,CAAC,mBAAmB;AAAA,EAE7C,MAAM,OAAO,aAA6C;AACxD,UAAM,eAAe,KAAK,KAAK,aAAa,mBAAmB;AAC/D,WAAO,WAAW,YAAY,IAAI,eAAe;AAAA,EACnD;AAAA,EAEA,MAAM,KAAK,aAAqB,cAA2C;AACzE,UAAM,CAAC,aAAa,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtD,SAAS,cAAc,OAAO;AAAA,MAC9B,SAAS,KAAK,KAAK,aAAa,cAAc,GAAG,OAAO,EAAE,MAAM,MAAM,IAAI;AAAA,IAC5E,CAAC;AAED,QAAI;AACJ,QAAI;AACF,iBAAW,KAAK,MAAM,WAAW;AAAA,IACnC,QAAQ;AACN,YAAM,IAAI,mBAAmB,cAAc,cAAc;AAAA,IAC3D;AAGA,UAAM,cAAc,oBAAI,IAAY;AACpC,QAAI,gBAAgB;AAClB,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,cAAc;AACrC,mBAAW,QAAQ,OAAO,KAAK,IAAI,gBAAgB,CAAC,CAAC,GAAG;AACtD,sBAAY,IAAI,IAAI;AAAA,QACtB;AACA,mBAAW,QAAQ,OAAO,KAAK,IAAI,mBAAmB,CAAC,CAAC,GAAG;AACzD,sBAAY,IAAI,IAAI;AAAA,QACtB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,eAAe,KAAK,cAAc,UAAU,WAAW;AAE7D,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,UAAuB,aAAwC;AACnF,UAAM,OAAqB,CAAC;AAE5B,QAAI,SAAS,UAAU;AAErB,iBAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,SAAS,QAAQ,GAAG;AAElE,YAAI,YAAY,GAAI;AAKpB,cAAM,OAAO,KAAK,mBAAmB,OAAO;AAC5C,YAAI,CAAC,QAAQ,CAAC,QAAQ,QAAS;AAG/B,YAAI,QAAQ,KAAM;AAElB,aAAK,KAAK;AAAA,UACR;AAAA,UACA,SAAS,QAAQ;AAAA,UACjB,QAAQ,YAAY,IAAI,IAAI;AAAA,UAC5B,WAAW;AAAA,UACX,MAAM,KAAK,UAAU,MAAM,QAAQ,OAAO;AAAA,QAC5C,CAAC;AAAA,MACH;AAAA,IACF,WAAW,SAAS,cAAc;AAEhC,WAAK,oBAAoB,SAAS,cAAc,aAAa,IAAI;AAAA,IACnE;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,UAAU,MAAc,SAAyB;AACvD,QAAI,KAAK,WAAW,GAAG,GAAG;AAExB,aAAO,cAAc,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO;AAAA,IAC/C;AACA,WAAO,WAAW,IAAI,IAAI,OAAO;AAAA,EACnC;AAAA;AAAA,EAGQ,mBAAmB,SAAgC;AAIzD,UAAM,QAAQ,QAAQ,MAAM,eAAe;AAC3C,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,WAAO,QAAQ;AAAA,EACjB;AAAA;AAAA,EAGQ,oBACN,SACA,aACA,QACM;AACN,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAI,KAAK,SAAS;AAChB,eAAO,KAAK;AAAA,UACV;AAAA,UACA,SAAS,KAAK;AAAA,UACd,QAAQ,YAAY,IAAI,IAAI;AAAA,UAC5B,WAAW;AAAA,UACX,MAAM,KAAK,UAAU,MAAM,KAAK,OAAO;AAAA,QACzC,CAAC;AAAA,MACH;AAEA,UAAI,KAAK,cAAc;AACrB,aAAK,oBAAoB,KAAK,cAAc,aAAa,MAAM;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACF;;;AE7IO,IAAM,eAAN,MAAgD;AAAA,EAC5C,YAAuB;AAAA,EACvB,gBAAgB,CAAC,oBAAoB;AAAA,EAE9C,MAAM,OAAO,cAA8C;AAEzD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,cAAsB,eAA4C;AAC3E,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACF;;;ACZO,IAAM,eAAN,MAAgD;AAAA,EAC5C,YAAuB;AAAA,EACvB,gBAAgB,CAAC,YAAY;AAAA,EAEtC,MAAM,OAAO,cAA8C;AAEzD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,cAAsB,eAA4C;AAC3E,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACF;;;ACXO,IAAM,kBAAN,MAAsB;AAAA,EACnB;AAAA,EAER,cAAc;AACZ,SAAK,WAAW;AAAA,MACd,IAAI,WAAW;AAAA,MACf,IAAI,aAAa;AAAA,MACjB,IAAI,aAAa;AAAA;AAAA,IAEnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,aAA0C;AAC5D,eAAW,WAAW,KAAK,UAAU;AACnC,YAAM,eAAe,MAAM,QAAQ,OAAO,WAAW;AACrD,UAAI,cAAc;AAChB,eAAO,QAAQ,KAAK,aAAa,YAAY;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,IAAI,gBAAgB,WAAW;AAAA,EACvC;AAAA;AAAA,EAGA,WAAW,WAAkD;AAC3D,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AAAA,EAC5D;AAAA;AAAA,EAGA,iBAA2B;AACzB,WAAO,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,EAC7C;AACF;;;AC9CA,SAAS,cAAAA,mBAAkB;AAgBpB,IAAM,qBAAN,MAAkD;AAAA,EAC9C,SAAqB;AAAA,EAE9B,SAAS,YAAwB,cAAsB,SAAe;AACpE,UAAM,MAAM,KAAK,SAAS,YAAY,WAAW;AACjD,UAAM,UAAU,KAAK,UAAU,KAAK,MAAM,CAAC;AAE3C,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa;AAAA,MACb;AAAA,MACA,gBAAgB,WAAW,aAAa;AAAA,MACxC,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACtC;AAAA,EACF;AAAA,EAEQ,SAAS,YAAwB,aAAmC;AAC1E,UAAM,cAAc,KAAK,mBAAmB,WAAW,WAAW;AAElE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,MACX,aAAa;AAAA,MACb,cAAc,YAAYA,YAAW,CAAC;AAAA,MACtC,SAAS;AAAA,MACT,UAAU;AAAA,QACR,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO;AAAA,UACL,YAAY;AAAA,YACV;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aAAa;AAAA,cACb,UAAU,EAAE,MAAM,SAAS;AAAA,cAC3B,oBAAoB;AAAA,gBAClB;AAAA,kBACE,MAAM;AAAA,kBACN,KAAK;AAAA,gBACP;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA;AAAA,QAEA,UAAU;AAAA,UACR,MAAM;AAAA,QACR;AAAA,QACA,WAAW;AAAA,UACT,MAAM;AAAA,UACN,MAAM;AAAA,UACN,WAAW;AAAA,UACX,UAAU,EAAE,MAAM,YAAY;AAAA,QAChC;AAAA,MACF;AAAA,MACA,YAAY,WAAW,aAAa,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG,CAAC;AAAA,MACtE,cAAc,KAAK,qBAAqB,UAAU;AAAA,IACpD;AAAA,EACF;AAAA;AAAA,EAGQ,YAAY,KAAqC;AACvD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,OAAO,IAAI,SAAS,aAAa;AAAA;AAAA,MAEjC,UAAU;AAAA,QACR,MAAM,KAAK,mBAAmB,IAAI,IAAI;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,mBAAmB,aAA6B;AACtD,QAAI,YAAY,WAAW,GAAG,GAAG;AAE/B,YAAM,QAAQ,YAAY,MAAM,GAAG,EAAE,CAAC;AACtC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,qBAAqB,YAAoD;AAC/E,UAAM,cAAc,WAAW,aAAa,IAAI,CAAC,MAAM,EAAE,IAAI;AAE7D,WAAO;AAAA,MACL;AAAA,QACE,KAAK;AAAA,QACL,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,mBAAmB,aAA6B;AACtD,UAAM,QAAQ,YAAY,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG;AACvD,WAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,EACpC;AACF;;;ACtIA,IAAM,eAAe;AACrB,IAAM,aAAa;AAcZ,IAAM,YAAN,MAAqC;AAAA,EACjC,WAAgC;AAAA,EAChC,OAAO;AAAA,EAER;AAAA,EAER,YAAY,WAA0B;AAEpC,SAAK,UAAU,aAAa,WAAW;AAAA,EACzC;AAAA,EAEA,MAAM,kBAAkB,cAAsD;AAC5E,QAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AAEvC,UAAM,WAA4B,CAAC;AAGnC,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,YAAY;AACxD,YAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,UAAU;AAClD,YAAM,aAAa,MAAM,KAAK,WAAW,KAAK;AAC9C,eAAS,KAAK,GAAG,UAAU;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,WAAW,cAAsD;AAC7E,UAAM,UAAU,aAAa,IAAI,CAAC,SAAS;AAAA,MACzC,SAAS,IAAI;AAAA,MACb,SAAS;AAAA,QACP,MAAM,IAAI;AAAA,QACV,WAAW,KAAK,aAAa,IAAI,SAAS;AAAA,MAC5C;AAAA,IACF,EAAE;AAEF,UAAM,WAAW,MAAM,KAAK,QAAQ,GAAG,YAAY,eAAe;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,IAClC,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,kBAAkB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,IAC5E;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,kBAAmC,CAAC;AAG1C,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,KAAK;AAC5C,YAAM,SAAS,KAAK,QAAQ,CAAC;AAC7B,YAAM,MAAM,aAAa,CAAC;AAE1B,UAAI,OAAO,SAAS,OAAO,MAAM,SAAS,GAAG;AAC3C,mBAAW,QAAQ,OAAO,OAAO;AAC/B,0BAAgB,KAAK,KAAK,iBAAiB,MAAM,GAAG,CAAC;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,iBAAiB,SAA2B,KAAgC;AAClF,UAAM,QAAQ,KAAK,aAAa,OAAO;AACvC,UAAM,WAAW,KAAK,gBAAgB,OAAO;AAE7C,WAAO;AAAA,MACL,IAAI,SAAS,QAAQ;AAAA,MACrB,SAAS,MAAM,KAAK,oBAAI,IAAI,CAAC,QAAQ,IAAI,GAAI,QAAQ,WAAW,CAAC,CAAE,CAAC,CAAC;AAAA,MACrE,SAAS,QAAQ,WAAW,QAAQ,SAAS,MAAM,GAAG,GAAG,KAAK;AAAA,MAC9D,UAAU,SAAS;AAAA,MACnB,WAAW,SAAS;AAAA,MACpB,aAAa,IAAI;AAAA,MACjB,WAAW,IAAI;AAAA,MACf,sBAAsB,KAAK,qBAAqB,SAAS,IAAI,IAAI;AAAA,MACjE,cAAc,KAAK,oBAAoB,SAAS,IAAI,IAAI;AAAA,MACxD,iBAAiB;AAAA;AAAA,MACjB,QAAQ;AAAA,MACR,cAAc,iCAAiC,QAAQ,EAAE;AAAA,MACzD,aAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAGQ,aAAa,MAAuC;AAE1D,QAAI,KAAK,GAAG,WAAW,MAAM,EAAG,QAAO,KAAK;AAG5C,QAAI,KAAK,SAAS;AAChB,YAAM,MAAM,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,CAAC;AACzD,UAAI,IAAK,QAAO;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,gBAAgB,MAA6D;AAEnF,QAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;AAC7C,iBAAW,OAAO,KAAK,UAAU;AAC/B,YAAI,IAAI,SAAS,WAAW;AAC1B,gBAAM,QAAQ,KAAK,eAAe,IAAI,KAAK;AAC3C,cAAI,UAAU,MAAM;AAClB,mBAAO,EAAE,OAAO,KAAK,gBAAgB,KAAK,GAAG,MAAM;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,mBAAmB,UAAU;AACpC,YAAM,IAAI,KAAK,kBAAkB,SAAS,YAAY;AACtD,UAAI,CAAC,YAAY,QAAQ,UAAU,KAAK,EAAE,SAAS,CAAC,GAAG;AACrD,eAAO,EAAE,OAAO,EAAc;AAAA,MAChC;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,UAAU;AAAA,EAC5B;AAAA;AAAA,EAGQ,eAAe,eAAsC;AAE3D,UAAM,MAAM,WAAW,aAAa;AACpC,QAAI,CAAC,MAAM,GAAG,KAAK,OAAO,KAAK,OAAO,GAAI,QAAO;AAIjD,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,gBAAgB,OAAyB;AAC/C,QAAI,SAAS,EAAK,QAAO;AACzB,QAAI,SAAS,EAAK,QAAO;AACzB,QAAI,SAAS,EAAK,QAAO;AACzB,QAAI,QAAQ,EAAK,QAAO;AACxB,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,qBAAqB,MAAwB,aAAyC;AAC5F,QAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,eAAW,YAAY,KAAK,UAAU;AACpC,UAAI,SAAS,SAAS,SAAS,eAAe,SAAS,QAAQ;AAC7D,mBAAW,SAAS,SAAS,QAAQ;AACnC,cAAI,MAAM,QAAQ;AAChB,kBAAM,aAAa,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG;AAC3D,kBAAM,QAAQ,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,KAAK,GAAG;AACjD,gBAAI,cAAc,MAAO,QAAO,KAAK,UAAU,MAAM,KAAK;AAC1D,gBAAI,WAAY,QAAO,KAAK,UAAU;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,oBAAoB,MAAwB,aAAyC;AAC3F,QAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,eAAW,YAAY,KAAK,UAAU;AACpC,UAAI,SAAS,SAAS,SAAS,eAAe,SAAS,QAAQ;AAC7D,mBAAW,SAAS,SAAS,QAAQ;AACnC,cAAI,MAAM,QAAQ;AAChB,kBAAM,QAAQ,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,KAAK,GAAG;AACjD,gBAAI,MAAO,QAAO;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,aAAa,WAA2B;AAC9C,UAAM,MAA8B;AAAA,MAClC,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,KAAK;AAAA,MACL,IAAI;AAAA,IACN;AACA,WAAO,IAAI,SAAS,KAAK;AAAA,EAC3B;AACF;;;AC3MO,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EAER,YAAY,SAAuB;AACjC,SAAK,UAAU,WAAW;AAAA,MACxB,IAAI,UAAU;AAAA;AAAA,IAEhB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,cAAqD;AAC/D,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,iBAAwC,CAAC;AAC/C,UAAM,eAAiE,CAAC;AACxE,UAAM,WAA4B,CAAC;AAGnC,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,KAAK,QAAQ,IAAI,OAAO,WAAW;AACjC,cAAM,QAAQ,MAAM,OAAO,kBAAkB,YAAY;AACzD,eAAO,EAAE,UAAU,OAAO,UAAU,MAAM;AAAA,MAC5C,CAAC;AAAA,IACH;AAEA,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,WAAW,aAAa;AACjC,uBAAe,KAAK,OAAO,MAAM,QAAQ;AACzC,iBAAS,KAAK,GAAG,OAAO,MAAM,KAAK;AAAA,MACrC,OAAO;AAEL,cAAM,cAAc,QAAQ,QAAQ,MAAM;AAC1C,cAAM,WAAW,KAAK,QAAQ,WAAW,EAAE;AAC3C,qBAAa,KAAK;AAAA,UAChB,QAAQ;AAAA,UACR,OAAO,OAAO,kBAAkB,QAAQ,OAAO,OAAO,UAAU,OAAO,OAAO,MAAM;AAAA,QACtF,CAAC;AAAA,MACH;AAAA,IACF;AAGA,UAAM,eAAe,KAAK,2BAA2B,QAAQ;AAE7D,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,MACA,iBAAiB,KAAK,IAAI,IAAI;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,2BAA2B,OAAyC;AAC1E,UAAM,QAAQ,oBAAI,IAA2B;AAE7C,eAAW,QAAQ,OAAO;AAGxB,YAAM,MAAM,GAAG,KAAK,EAAE,KAAK,KAAK,WAAW;AAC3C,YAAM,WAAW,MAAM,IAAI,GAAG;AAE9B,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,KAAK,IAAI;AAAA,MACrB,OAAO;AAEL,cAAM,IAAI,KAAK,KAAK,gBAAgB,UAAU,IAAI,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,MAAM,OAAO,CAAC;AAAA,EAClC;AAAA;AAAA,EAGQ,gBAAgB,GAAkB,GAAiC;AACzE,QAAI,SAAS;AACb,QAAI,SAAS;AAEb,QAAI,EAAE,cAAc,OAAW;AAC/B,QAAI,EAAE,cAAc,OAAW;AAC/B,QAAI,EAAE,aAAc;AACpB,QAAI,EAAE,aAAc;AACpB,QAAI,EAAE,qBAAsB;AAC5B,QAAI,EAAE,qBAAsB;AAC5B,QAAI,EAAE,aAAa,UAAW;AAC9B,QAAI,EAAE,aAAa,UAAW;AAI9B,UAAM,QAAQ,CAAC,QACb,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,UAAa,MAAM,IAAI,CAAC;AAEzF,UAAM,SAAS,SAAS,SACpB,EAAE,GAAG,MAAM,CAAuC,GAAG,GAAG,MAAM,CAAuC,EAAE,IACvG,EAAE,GAAG,MAAM,CAAuC,GAAG,GAAG,MAAM,CAAuC,EAAE;AAG3G,UAAM,aAAa,oBAAI,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,EAAE,OAAO,CAAC;AACvD,WAAO,UAAU,MAAM,KAAK,UAAU;AAGtC,WAAO,kBAAkB,EAAE,mBAAmB,EAAE;AAEhD,WAAO;AAAA,EACT;AACF;;;ACnHO,IAAM,kBAAN,MAA0C;AAAA,EACtC,OAAO;AAAA,EAEhB,OAAO,QAA8B;AACnC,UAAM,QAAkB,CAAC;AAEzB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,4RAAiD;AAC5D,UAAM,KAAK,2DAAiD;AAC5D,UAAM,KAAK,4RAAiD;AAC5D,UAAM,KAAK,EAAE;AAGb,UAAM,KAAK,mBAAmB,OAAO,QAAQ,IAAI,EAAE;AACnD,UAAM,KAAK,mBAAmB,OAAO,QAAQ,SAAS,EAAE;AACxD,UAAM,KAAK,mBAAmB,OAAO,QAAQ,eAAe,EAAE;AAC9D,UAAM,KAAK,mBAAmB,OAAO,WAAW,EAAE;AAClD,UAAM,KAAK,EAAE;AAGb,UAAM,KAAK,4BAAuB,OAAO,KAAK,MAAM,KAAK,OAAO,KAAK,WAAW,GAAG;AACnF,UAAM,KAAK,mBAAmB,OAAO,KAAK,cAAc,EAAE;AAC1D,UAAM,KAAK,EAAE;AAGb,UAAM,QAAQ,OAAO,SAAS;AAC9B,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,KAAK,yCAAoC;AAAA,IACjD,OAAO;AACL,YAAM,KAAK,YAAO,MAAM,MAAM,gBAAgB,MAAM,WAAW,IAAI,MAAM,KAAK,SAAS;AACvF,YAAM,KAAK,EAAE;AAGb,YAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,CAAC;AAE9F,iBAAW,QAAQ,QAAQ;AACzB,cAAM,QAAQ,cAAc,KAAK,QAAQ;AACzC,cAAM,MAAM,KAAK,eAAe,gBAAW,KAAK,YAAY,KAAK;AACjE,cAAM,KAAK,OAAO,KAAK,KAAK,KAAK,EAAE,EAAE;AACrC,cAAM,KAAK,cAAc,KAAK,WAAW,IAAI,KAAK,wBAAwB,GAAG,GAAG,GAAG,EAAE;AACrF,cAAM,KAAK,cAAc,KAAK,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AACrD,YAAI,KAAK,iBAAiB;AACxB,gBAAM,KAAK,2EAA+D;AAAA,QAC5E;AACA,cAAM,KAAK,EAAE;AAAA,MACf;AAAA,IACF;AAGA,UAAM,UAAU,OAAO,SAAS,eAAe,KAAK,IAAI;AACxD,UAAM,KAAK,sBAAsB,OAAO,KAAK,OAAO,SAAS,eAAe,KAAK;AAEjF,QAAI,OAAO,SAAS,aAAa,SAAS,GAAG;AAC3C,iBAAW,OAAO,OAAO,SAAS,cAAc;AAC9C,cAAM,KAAK,YAAO,IAAI,MAAM,KAAK,IAAI,KAAK,EAAE;AAAA,MAC9C;AAAA,IACF;AAGA,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,iDAAmB;AAC9B,UAAM,KAAK,YAAY,OAAO,QAAQ,oBAAoB,kBAC3C,OAAO,QAAQ,QAAQ,cAC3B,OAAO,QAAQ,IAAI,gBACjB,OAAO,QAAQ,MAAM,aACxB,OAAO,QAAQ,GAAG,EAAE;AAE9B,QAAI,OAAO,QAAQ,kBAAkB,GAAG;AACtC,YAAM,KAAK,eAAQ,OAAO,QAAQ,eAAe,sDAAiD;AAAA,IACpG;AAEA,UAAM,KAAK,EAAE;AACb,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AACF;AAEA,SAAS,cAAc,GAAqB;AAC1C,QAAM,QAAkC;AAAA,IACtC,UAAU;AAAA,IAAG,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAG,KAAK;AAAA,IAAG,SAAS;AAAA,EACpD;AACA,SAAO,MAAM,CAAC,KAAK;AACrB;AAEA,SAAS,cAAc,GAAqB;AAC1C,QAAM,SAAmC;AAAA,IACvC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,SAAS;AAAA,EACX;AACA,SAAO,OAAO,CAAC,KAAK;AACtB;;;ATjFA,eAAsB,KAAK,QAA6C;AACtE,QAAM;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,IACb,eAAe;AAAA,EACjB,IAAI;AAGJ,QAAM,WAAW,IAAI,gBAAgB;AACrC,QAAM,aAAa,MAAM,SAAS,cAAc,WAAW;AAG3D,QAAM,gBAAgB,IAAI,mBAAmB;AAC7C,QAAM,OAAO,cAAc,SAAS,UAAU;AAG9C,QAAM,UAAU,YAAY,KAAK,SAAS,OAAO;AAGjD,MAAI;AACJ,MAAI,cAAc;AAChB,eAAW;AAAA,MACT,iBAAiB,CAAC;AAAA,MAClB,gBAAgB,CAAC;AAAA,MACjB,cAAc,CAAC;AAAA,MACf,iBAAiB;AAAA,IACnB;AAAA,EACF,OAAO;AACL,UAAM,aAAa,IAAI,cAAc;AACrC,eAAW,MAAM,WAAW,MAAM,WAAW,YAAY;AAAA,EAC3D;AAGA,QAAM,UAAU;AAAA,IACd,mBAAmB,WAAW,aAAa;AAAA,IAC3C,sBAAsB,SAAS,gBAAgB;AAAA,IAC/C,UAAU,SAAS,gBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,UAAU,EAAE;AAAA,IAC5E,MAAM,SAAS,gBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAAA,IACpE,QAAQ,SAAS,gBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE;AAAA,IACxE,KAAK,SAAS,gBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,KAAK,EAAE;AAAA,IAClE,iBAAiB,SAAS,gBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE;AAAA,EAC7E;AAEA,QAAM,SAAuB;AAAA,IAC3B,SAAS;AAAA,MACP,MAAM;AAAA,MACN,WAAW,WAAW;AAAA,MACtB,iBAAiB,WAAW,aAAa;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AAEA,SAAO;AACT;AAKO,SAAS,aAAa,QAAsB,WAA8B;AAC/E,QAAMC,iBAA0C;AAAA,IAC9C,UAAU;AAAA,IAAG,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAG,KAAK;AAAA,IAAG,SAAS;AAAA,EACpD;AACA,QAAM,iBAAiBA,eAAc,SAAS,KAAK;AAEnD,SAAO,OAAO,SAAS,gBAAgB;AAAA,IACrC,CAAC,MAAMA,eAAc,EAAE,QAAQ,KAAK;AAAA,EACtC;AACF;AAKO,SAAS,YAAY,QAA4B;AACtD,QAAM,WAAW,IAAI,gBAAgB;AACrC,UAAQ,IAAI,SAAS,OAAO,MAAM,CAAC;AACrC;","names":["randomUUID","severityOrder"]}
|
|
1
|
+
{"version":3,"sources":["../src/generate-sbom.ts","../src/scan.ts","../src/scanners/npm/npm-scanner.ts","../src/core/errors.ts","../src/scanners/nuget/nuget-scanner.ts","../src/scanners/cargo/cargo-scanner.ts","../src/scanners/registry.ts","../src/sbom/cyclonedx.ts","../src/cve/osv.ts","../src/cve/aggregator.ts","../src/reporters/console.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport type {\n GenerateSbomInput,\n GenerateSbomResult,\n SbomDependency,\n Ecosystem,\n} from './core/types.js';\n\n/**\n * Generates an NTIA-compliant CycloneDX 1.7 SBOM from structured dependency data.\n *\n * This is a **pure function** — no filesystem access, no network calls, no side effects.\n * It takes a project name, version, and list of dependencies, and returns a complete\n * CycloneDX 1.7 JSON SBOM that passes NTIA minimum-element validation.\n *\n * @example\n * ```ts\n * import { generateSbom } from 'verimu';\n *\n * const result = generateSbom({\n * projectName: 'my-app',\n * projectVersion: '1.0.0',\n * dependencies: [\n * { name: 'express', version: '4.18.2', ecosystem: 'npm' },\n * { name: '@types/node', version: '20.11.5', ecosystem: 'npm', direct: false },\n * ],\n * });\n *\n * console.log(result.componentCount); // 2\n * console.log(result.content); // formatted JSON string\n * ```\n */\nexport function generateSbom(input: GenerateSbomInput): GenerateSbomResult {\n const {\n projectName,\n projectVersion = '0.0.0',\n dependencies,\n } = input;\n\n const timestamp = new Date().toISOString();\n\n // Resolve PURLs for any deps that don't have one\n const resolvedDeps = dependencies.map((dep) => ({\n ...dep,\n direct: dep.direct ?? true,\n purl: dep.purl ?? buildPurl(dep.name, dep.version, dep.ecosystem),\n }));\n\n const rootPurl = buildPurl(projectName, projectVersion, 'npm');\n\n const sbom = {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp,\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: '0.0.1',\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n { type: 'website', url: 'https://verimu.com' },\n ],\n },\n ],\n },\n supplier: { name: projectName },\n component: {\n type: 'application',\n name: projectName,\n version: projectVersion,\n 'bom-ref': rootPurl,\n supplier: { name: projectName },\n },\n },\n components: resolvedDeps.map((dep) => ({\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n supplier: { name: deriveSupplierName(dep.name) },\n })),\n dependencies: [\n {\n ref: rootPurl,\n dependsOn: resolvedDeps.map((d) => d.purl),\n },\n ],\n };\n\n const content = JSON.stringify(sbom, null, 2);\n\n return {\n sbom,\n content,\n componentCount: resolvedDeps.length,\n specVersion: '1.7',\n generatedAt: timestamp,\n };\n}\n\n// ─── Internal helpers ───────────────────────────────────────────\n\nconst PURL_TYPE_MAP: Record<Ecosystem, string> = {\n npm: 'npm',\n nuget: 'nuget',\n cargo: 'cargo',\n maven: 'maven',\n pip: 'pypi',\n go: 'golang',\n};\n\n/**\n * Builds a Package URL (purl) per the purl spec.\n *\n * For npm scoped packages, the @ prefix is percent-encoded as %40:\n * @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n *\n * See: https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md\n */\nfunction buildPurl(name: string, version: string, ecosystem: Ecosystem): string {\n const type = PURL_TYPE_MAP[ecosystem] || ecosystem;\n\n if (ecosystem === 'npm' && name.startsWith('@')) {\n return `pkg:${type}/%40${name.slice(1)}@${version}`;\n }\n\n return `pkg:${type}/${name}@${version}`;\n}\n\n/**\n * Derives supplier name from a package name.\n * Scoped packages: \"@vue/reactivity\" → \"@vue\"\n * Unscoped packages: \"express\" → \"express\"\n */\nfunction deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n return packageName.split('/')[0];\n }\n return packageName;\n}\n","import { writeFile } from 'fs/promises';\nimport { ScannerRegistry } from './scanners/registry.js';\nimport { CycloneDxGenerator } from './sbom/cyclonedx.js';\nimport { CveAggregator } from './cve/aggregator.js';\nimport { ConsoleReporter } from './reporters/console.js';\nimport type { VerimuConfig, VerimuReport, Severity } from './core/types.js';\n\n/**\n * Main scan pipeline — orchestrates the full Verimu workflow:\n * 1. Detect ecosystem & parse lockfile\n * 2. Generate CycloneDX SBOM\n * 3. Check dependencies for CVEs\n * 4. Produce report\n * 5. Optionally upload snapshot to Verimu API\n */\nexport async function scan(config: VerimuConfig): Promise<VerimuReport> {\n const {\n projectPath,\n sbomOutput = './sbom.cdx.json',\n skipCveCheck = false,\n } = config;\n\n // 1. Scan dependencies\n const registry = new ScannerRegistry();\n const scanResult = await registry.detectAndScan(projectPath);\n\n // 2. Generate SBOM\n const sbomGenerator = new CycloneDxGenerator();\n const sbom = sbomGenerator.generate(scanResult);\n\n // 3. Write SBOM to disk\n await writeFile(sbomOutput, sbom.content, 'utf-8');\n\n // 4. Check CVEs (unless skipped)\n let cveCheck;\n if (skipCveCheck) {\n cveCheck = {\n vulnerabilities: [],\n sourcesQueried: [],\n sourceErrors: [],\n checkDurationMs: 0,\n };\n } else {\n const aggregator = new CveAggregator();\n cveCheck = await aggregator.check(scanResult.dependencies);\n }\n\n // 5. Build report\n const summary = {\n totalDependencies: scanResult.dependencies.length,\n totalVulnerabilities: cveCheck.vulnerabilities.length,\n critical: cveCheck.vulnerabilities.filter((v) => v.severity === 'CRITICAL').length,\n high: cveCheck.vulnerabilities.filter((v) => v.severity === 'HIGH').length,\n medium: cveCheck.vulnerabilities.filter((v) => v.severity === 'MEDIUM').length,\n low: cveCheck.vulnerabilities.filter((v) => v.severity === 'LOW').length,\n exploitedInWild: cveCheck.vulnerabilities.filter((v) => v.exploitedInWild).length,\n };\n\n const report: VerimuReport = {\n project: {\n path: projectPath,\n ecosystem: scanResult.ecosystem,\n dependencyCount: scanResult.dependencies.length,\n },\n sbom,\n cveCheck,\n summary,\n generatedAt: new Date().toISOString(),\n };\n\n return report;\n}\n\n/**\n * Determines if the scan should fail CI based on severity threshold.\n */\nexport function shouldFailCi(report: VerimuReport, threshold: Severity): boolean {\n const severityOrder: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n const thresholdLevel = severityOrder[threshold] ?? 4;\n\n return report.cveCheck.vulnerabilities.some(\n (v) => severityOrder[v.severity] <= thresholdLevel\n );\n}\n\n/**\n * Prints a console report to stdout.\n */\nexport function printReport(report: VerimuReport): void {\n const reporter = new ConsoleReporter();\n console.log(reporter.report(report));\n}\n","import { readFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport path from 'path';\nimport type { DependencyScanner } from '../scanner.interface.js';\nimport type { Dependency, Ecosystem, ScanResult } from '../../core/types.js';\nimport { LockfileParseError } from '../../core/errors.js';\n\n/**\n * npm / Node.js dependency scanner.\n *\n * Parses package-lock.json (v2/v3 format) to extract the full\n * resolved dependency tree. Also reads package.json to determine\n * which dependencies are direct vs transitive.\n */\nexport class NpmScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'npm';\n readonly lockfileNames = ['package-lock.json'];\n\n async detect(projectPath: string): Promise<string | null> {\n const lockfilePath = path.join(projectPath, 'package-lock.json');\n return existsSync(lockfilePath) ? lockfilePath : null;\n }\n\n async scan(projectPath: string, lockfilePath: string): Promise<ScanResult> {\n const [lockfileRaw, packageJsonRaw] = await Promise.all([\n readFile(lockfilePath, 'utf-8'),\n readFile(path.join(projectPath, 'package.json'), 'utf-8').catch(() => null),\n ]);\n\n let lockfile: NpmLockfile;\n try {\n lockfile = JSON.parse(lockfileRaw);\n } catch {\n throw new LockfileParseError(lockfilePath, 'Invalid JSON');\n }\n\n // Determine direct dependency names from package.json\n const directNames = new Set<string>();\n if (packageJsonRaw) {\n try {\n const pkg = JSON.parse(packageJsonRaw);\n for (const name of Object.keys(pkg.dependencies ?? {})) {\n directNames.add(name);\n }\n for (const name of Object.keys(pkg.devDependencies ?? {})) {\n directNames.add(name);\n }\n } catch {\n // If package.json can't be parsed, all deps are \"unknown\" direct status\n }\n }\n\n const dependencies = this.parseLockfile(lockfile, directNames);\n\n return {\n projectPath,\n ecosystem: 'npm',\n dependencies,\n lockfilePath,\n scannedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Parses package-lock.json and extracts dependencies.\n * Supports lockfile v2 and v3 (uses the `packages` field).\n * Falls back to `dependencies` field for lockfile v1.\n */\n private parseLockfile(lockfile: NpmLockfile, directNames: Set<string>): Dependency[] {\n const deps: Dependency[] = [];\n\n if (lockfile.packages) {\n // Lockfile v2/v3: `packages` is a flat map of \"node_modules/name\" → info\n for (const [pkgPath, pkgInfo] of Object.entries(lockfile.packages)) {\n // Skip the root package (empty string key)\n if (pkgPath === '') continue;\n\n // Extract package name from the path\n // e.g., \"node_modules/express\" → \"express\"\n // e.g., \"node_modules/@types/node\" → \"@types/node\"\n const name = this.extractPackageName(pkgPath);\n if (!name || !pkgInfo.version) continue;\n\n // Skip link: true entries (workspace references)\n if (pkgInfo.link) continue;\n\n deps.push({\n name,\n version: pkgInfo.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, pkgInfo.version),\n });\n }\n } else if (lockfile.dependencies) {\n // Lockfile v1 fallback: `dependencies` is a nested tree\n this.parseDependenciesV1(lockfile.dependencies, directNames, deps);\n }\n\n return deps;\n }\n\n /**\n * Builds a purl (Package URL) for an npm package.\n *\n * Per the purl spec (https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md):\n * \"The npm scope @ sign prefix is always percent encoded.\"\n *\n * So @types/node@20.11.5 → pkg:npm/%40types/node@20.11.5\n * And express@4.18.2 → pkg:npm/express@4.18.2\n */\n private buildPurl(name: string, version: string): string {\n if (name.startsWith('@')) {\n // Scoped: encode the @ as %40 per purl spec\n return `pkg:npm/%40${name.slice(1)}@${version}`;\n }\n return `pkg:npm/${name}@${version}`;\n }\n\n /** Extracts the package name from a node_modules path */\n private extractPackageName(pkgPath: string): string | null {\n // \"node_modules/@scope/name\" → \"@scope/name\"\n // \"node_modules/name\" → \"name\"\n // \"node_modules/a/node_modules/b\" → \"b\" (nested)\n const parts = pkgPath.split('node_modules/');\n const last = parts[parts.length - 1];\n return last || null;\n }\n\n /** Recursively parses lockfile v1 `dependencies` tree */\n private parseDependenciesV1(\n depsObj: Record<string, NpmLockfileV1Dep>,\n directNames: Set<string>,\n result: Dependency[]\n ): void {\n for (const [name, info] of Object.entries(depsObj)) {\n if (info.version) {\n result.push({\n name,\n version: info.version,\n direct: directNames.has(name),\n ecosystem: 'npm',\n purl: this.buildPurl(name, info.version),\n });\n }\n // Recurse into nested dependencies\n if (info.dependencies) {\n this.parseDependenciesV1(info.dependencies, directNames, result);\n }\n }\n }\n}\n\n// ─── Types for package-lock.json parsing ─────────────────────────\n\ninterface NpmLockfile {\n name?: string;\n version?: string;\n lockfileVersion?: number;\n packages?: Record<string, NpmLockfilePackage>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n\ninterface NpmLockfilePackage {\n version?: string;\n resolved?: string;\n integrity?: string;\n dev?: boolean;\n optional?: boolean;\n link?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface NpmLockfileV1Dep {\n version?: string;\n resolved?: string;\n integrity?: string;\n requires?: Record<string, string>;\n dependencies?: Record<string, NpmLockfileV1Dep>;\n}\n","/** Base error for all Verimu errors */\nexport class VerimuError extends Error {\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'VerimuError';\n }\n}\n\n/** Thrown when no supported lockfile is found */\nexport class NoLockfileError extends VerimuError {\n constructor(projectPath: string) {\n super(\n `No supported lockfile found in ${projectPath}. ` +\n `Supported: package-lock.json (npm), packages.lock.json (NuGet), Cargo.lock (Rust)`,\n 'NO_LOCKFILE'\n );\n this.name = 'NoLockfileError';\n }\n}\n\n/** Thrown when lockfile parsing fails */\nexport class LockfileParseError extends VerimuError {\n constructor(lockfilePath: string, reason: string) {\n super(`Failed to parse ${lockfilePath}: ${reason}`, 'LOCKFILE_PARSE_ERROR');\n this.name = 'LockfileParseError';\n }\n}\n\n/** Thrown when a CVE source query fails */\nexport class CveSourceError extends VerimuError {\n constructor(source: string, reason: string) {\n super(`CVE source \"${source}\" failed: ${reason}`, 'CVE_SOURCE_ERROR');\n this.name = 'CveSourceError';\n }\n}\n\n/** Thrown when API key is required but missing */\nexport class ApiKeyRequiredError extends VerimuError {\n constructor(feature: string) {\n super(\n `API key required for \"${feature}\". Get one at https://verimu.com/dashboard`,\n 'API_KEY_REQUIRED'\n );\n this.name = 'ApiKeyRequiredError';\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * C# / NuGet dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - packages.lock.json (NuGet lock file)\n * - *.csproj files (for direct dependency list)\n */\nexport class NugetScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'nuget';\n readonly lockfileNames = ['packages.lock.json'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for packages.lock.json\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('NuGet scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from '../scanner.interface.js';\nimport type { Ecosystem, ScanResult } from '../../core/types.js';\n\n/**\n * Rust / Cargo dependency scanner (STUB).\n *\n * TODO: Implement parsing of:\n * - Cargo.lock (resolved dependency tree)\n * - Cargo.toml (for direct dependency list)\n */\nexport class CargoScanner implements DependencyScanner {\n readonly ecosystem: Ecosystem = 'cargo';\n readonly lockfileNames = ['Cargo.lock'];\n\n async detect(_projectPath: string): Promise<string | null> {\n // TODO: Check for Cargo.lock\n return null;\n }\n\n async scan(_projectPath: string, _lockfilePath: string): Promise<ScanResult> {\n throw new Error('Cargo scanner not yet implemented. Coming soon.');\n }\n}\n","import type { DependencyScanner } from './scanner.interface.js';\nimport type { ScanResult } from '../core/types.js';\nimport { NpmScanner } from './npm/npm-scanner.js';\nimport { NugetScanner } from './nuget/nuget-scanner.js';\nimport { CargoScanner } from './cargo/cargo-scanner.js';\nimport { NoLockfileError } from '../core/errors.js';\n\n/**\n * Registry of all available dependency scanners.\n * Auto-detects the correct scanner for a given project.\n */\nexport class ScannerRegistry {\n private scanners: DependencyScanner[];\n\n constructor() {\n this.scanners = [\n new NpmScanner(),\n new NugetScanner(),\n new CargoScanner(),\n // Add new scanners here as they're implemented\n ];\n }\n\n /**\n * Auto-detects the project's ecosystem and scans dependencies.\n * Tries each registered scanner in order until one matches.\n */\n async detectAndScan(projectPath: string): Promise<ScanResult> {\n for (const scanner of this.scanners) {\n const lockfilePath = await scanner.detect(projectPath);\n if (lockfilePath) {\n return scanner.scan(projectPath, lockfilePath);\n }\n }\n throw new NoLockfileError(projectPath);\n }\n\n /** Returns a specific scanner by ecosystem name */\n getScanner(ecosystem: string): DependencyScanner | undefined {\n return this.scanners.find((s) => s.ecosystem === ecosystem);\n }\n\n /** Lists all registered ecosystems */\n listEcosystems(): string[] {\n return this.scanners.map((s) => s.ecosystem);\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SbomGenerator } from './generator.interface.js';\nimport type { ScanResult, Sbom, SbomFormat, Dependency } from '../core/types.js';\n\n/**\n * Generates CycloneDX 1.7 JSON SBOMs.\n *\n * CycloneDX is the preferred SBOM format for CRA compliance.\n * Spec: https://cyclonedx.org/docs/1.7/json/\n *\n * NTIA minimum elements are satisfied:\n * - metadata.supplier (supplier of the root software)\n * - components[].supplier (supplier of each dependency)\n * - components[].name, version, purl, bom-ref\n * - dependencies[] graph\n */\nexport class CycloneDxGenerator implements SbomGenerator {\n readonly format: SbomFormat = 'cyclonedx-json';\n\n generate(scanResult: ScanResult, toolVersion: string = '0.1.0'): Sbom {\n const bom = this.buildBom(scanResult, toolVersion);\n const content = JSON.stringify(bom, null, 2);\n\n return {\n format: 'cyclonedx-json',\n specVersion: '1.7',\n content,\n componentCount: scanResult.dependencies.length,\n generatedAt: new Date().toISOString(),\n };\n }\n\n private buildBom(scanResult: ScanResult, toolVersion: string): CycloneDxBom {\n const projectName = this.extractProjectName(scanResult.projectPath);\n\n return {\n $schema: 'http://cyclonedx.org/schema/bom-1.7.schema.json',\n bomFormat: 'CycloneDX',\n specVersion: '1.7',\n serialNumber: `urn:uuid:${randomUUID()}`,\n version: 1,\n metadata: {\n timestamp: new Date().toISOString(),\n tools: {\n components: [\n {\n type: 'application',\n name: 'verimu',\n version: toolVersion,\n description: 'Verimu CRA Compliance Scanner',\n supplier: { name: 'Verimu' },\n externalReferences: [\n {\n type: 'website',\n url: 'https://verimu.com',\n },\n ],\n },\n ],\n },\n // NTIA: metadata.supplier — the org supplying the root software\n supplier: {\n name: projectName,\n },\n component: {\n type: 'application',\n name: projectName,\n 'bom-ref': 'root-component',\n supplier: { name: projectName },\n },\n },\n components: scanResult.dependencies.map((dep) => this.toComponent(dep)),\n dependencies: this.buildDependencyGraph(scanResult),\n };\n }\n\n /** Converts a Verimu Dependency to a CycloneDX component */\n private toComponent(dep: Dependency): CycloneDxComponent {\n return {\n type: 'library',\n name: dep.name,\n version: dep.version,\n purl: dep.purl,\n 'bom-ref': dep.purl,\n scope: dep.direct ? 'required' : 'optional',\n // NTIA: component.supplier — derived from npm scope or package name\n supplier: {\n name: this.deriveSupplierName(dep.name),\n },\n };\n }\n\n /**\n * Derives a supplier name from a package name.\n *\n * For scoped packages like \"@vue/reactivity\" → \"@vue\"\n * For unscoped packages like \"express\" → \"express\"\n *\n * This is the same heuristic used by Syft, Trivy, and other SBOM tools\n * when registry metadata (author/publisher) isn't available from the lockfile.\n */\n private deriveSupplierName(packageName: string): string {\n if (packageName.startsWith('@')) {\n // Scoped package: \"@scope/name\" → \"@scope\"\n const scope = packageName.split('/')[0];\n return scope;\n }\n return packageName;\n }\n\n /**\n * Builds the dependency graph section of the SBOM.\n *\n * The root component depends on all dependencies (direct + transitive).\n * This ensures a single root node in the graph, which NTIA validators expect.\n *\n * We include ALL deps under root (not just direct) because from a flat lockfile\n * we can't reliably reconstruct which transitive dep belongs to which direct dep.\n * This is still valid per the CycloneDX spec — it represents a complete but flat\n * dependency relationship.\n */\n private buildDependencyGraph(scanResult: ScanResult): CycloneDxDependencyEntry[] {\n const allDepPurls = scanResult.dependencies.map((d) => d.purl);\n\n return [\n {\n ref: 'root-component',\n dependsOn: allDepPurls,\n },\n ];\n }\n\n /** Extracts project name from path */\n private extractProjectName(projectPath: string): string {\n const parts = projectPath.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1] || 'unknown-project';\n }\n}\n\n// ─── CycloneDX 1.7 JSON Types ──────────────────────────────────\n\ninterface OrganizationalEntity {\n name: string;\n url?: string[];\n contact?: Array<{ name?: string; email?: string; phone?: string }>;\n}\n\ninterface CycloneDxBom {\n $schema: string;\n bomFormat: string;\n specVersion: string;\n serialNumber: string;\n version: number;\n metadata: {\n timestamp: string;\n tools: {\n components: Array<{\n type: string;\n name: string;\n version: string;\n description?: string;\n supplier?: OrganizationalEntity;\n externalReferences?: Array<{ type: string; url: string }>;\n }>;\n };\n supplier: OrganizationalEntity;\n component: {\n type: string;\n name: string;\n 'bom-ref': string;\n supplier: OrganizationalEntity;\n };\n };\n components: CycloneDxComponent[];\n dependencies: CycloneDxDependencyEntry[];\n}\n\ninterface CycloneDxComponent {\n type: string;\n name: string;\n version: string;\n purl: string;\n 'bom-ref': string;\n scope?: string;\n supplier: OrganizationalEntity;\n}\n\ninterface CycloneDxDependencyEntry {\n ref: string;\n dependsOn: string[];\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, Vulnerability, VulnerabilitySource, Severity } from '../core/types.js';\n\nconst OSV_API_BASE = 'https://api.osv.dev/v1';\nconst BATCH_SIZE = 1000; // OSV querybatch supports up to 1000\n\n/**\n * OSV.dev (Google Open Source Vulnerabilities) CVE source.\n *\n * Primary CVE source for Verimu because:\n * - Supports direct package name + ecosystem + version queries\n * - Has batch query endpoint for efficiency\n * - No authentication required\n * - Covers npm, PyPI, Go, Rust, Maven, NuGet, etc.\n * - Aggregates data from GitHub Advisory, NVD, and others\n *\n * API docs: https://google.github.io/osv.dev/api/\n */\nexport class OsvSource implements CveSource {\n readonly sourceId: VulnerabilitySource = 'osv';\n readonly name = 'OSV.dev (Google Open Source Vulnerabilities)';\n\n private fetchFn: typeof fetch;\n\n constructor(fetchImpl?: typeof fetch) {\n // Allow injecting fetch for testing\n this.fetchFn = fetchImpl ?? globalThis.fetch;\n }\n\n async checkDependencies(dependencies: Dependency[]): Promise<Vulnerability[]> {\n if (dependencies.length === 0) return [];\n\n const allVulns: Vulnerability[] = [];\n\n // Process in batches of BATCH_SIZE\n for (let i = 0; i < dependencies.length; i += BATCH_SIZE) {\n const batch = dependencies.slice(i, i + BATCH_SIZE);\n const batchVulns = await this.queryBatch(batch);\n allVulns.push(...batchVulns);\n }\n\n return allVulns;\n }\n\n /** Uses OSV's /querybatch endpoint for efficient bulk lookups */\n private async queryBatch(dependencies: Dependency[]): Promise<Vulnerability[]> {\n const queries = dependencies.map((dep) => ({\n version: dep.version,\n package: {\n name: dep.name,\n ecosystem: this.mapEcosystem(dep.ecosystem),\n },\n }));\n\n const response = await this.fetchFn(`${OSV_API_BASE}/querybatch`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ queries }),\n });\n\n if (!response.ok) {\n throw new Error(`OSV API error: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OsvBatchResponse;\n const vulnerabilities: Vulnerability[] = [];\n\n // Each result in `results` corresponds to the query at the same index\n for (let i = 0; i < data.results.length; i++) {\n const result = data.results[i];\n const dep = dependencies[i];\n\n if (result.vulns && result.vulns.length > 0) {\n for (const vuln of result.vulns) {\n vulnerabilities.push(this.mapVulnerability(vuln, dep));\n }\n }\n }\n\n return vulnerabilities;\n }\n\n /** Maps an OSV vulnerability record to our Vulnerability type */\n private mapVulnerability(osvVuln: OsvVulnerability, dep: Dependency): Vulnerability {\n const cveId = this.extractCveId(osvVuln);\n const severity = this.extractSeverity(osvVuln);\n\n return {\n id: cveId || osvVuln.id,\n aliases: Array.from(new Set([osvVuln.id, ...(osvVuln.aliases ?? [])])),\n summary: osvVuln.summary ?? osvVuln.details?.slice(0, 200) ?? 'No description available',\n severity: severity.level,\n cvssScore: severity.score,\n packageName: dep.name,\n ecosystem: dep.ecosystem,\n affectedVersionRange: this.extractAffectedRange(osvVuln, dep.name),\n fixedVersion: this.extractFixedVersion(osvVuln, dep.name),\n exploitedInWild: false, // OSV doesn't track this — CISA KEV does\n source: 'osv',\n referenceUrl: `https://osv.dev/vulnerability/${osvVuln.id}`,\n publishedAt: osvVuln.published,\n };\n }\n\n /** Extracts CVE ID from aliases (prefers CVE-xxxx over GHSA-xxxx) */\n private extractCveId(vuln: OsvVulnerability): string | null {\n // Check the main ID first\n if (vuln.id.startsWith('CVE-')) return vuln.id;\n\n // Check aliases\n if (vuln.aliases) {\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\n if (cve) return cve;\n }\n\n return null;\n }\n\n /** Extracts severity from CVSS scores in the OSV record */\n private extractSeverity(vuln: OsvVulnerability): { level: Severity; score?: number } {\n // Try database_specific first (often has CVSS)\n if (vuln.severity && vuln.severity.length > 0) {\n for (const sev of vuln.severity) {\n if (sev.type === 'CVSS_V3') {\n const score = this.parseCvssScore(sev.score);\n if (score !== null) {\n return { level: this.scoreToSeverity(score), score };\n }\n }\n }\n }\n\n // Try to extract from database_specific\n if (vuln.database_specific?.severity) {\n const s = vuln.database_specific.severity.toUpperCase();\n if (['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].includes(s)) {\n return { level: s as Severity };\n }\n }\n\n return { level: 'UNKNOWN' };\n }\n\n /** Parses CVSS v3 vector string to extract the base score */\n private parseCvssScore(vectorOrScore: string): number | null {\n // Could be a raw score like \"7.5\" or a vector like \"CVSS:3.1/AV:N/AC:L/...\"\n const num = parseFloat(vectorOrScore);\n if (!isNaN(num) && num >= 0 && num <= 10) return num;\n\n // If it's a vector string, we'd need to calculate — for now return null\n // and rely on severity text\n return null;\n }\n\n /** Converts a CVSS score (0-10) to a severity level */\n private scoreToSeverity(score: number): Severity {\n if (score >= 9.0) return 'CRITICAL';\n if (score >= 7.0) return 'HIGH';\n if (score >= 4.0) return 'MEDIUM';\n if (score > 0.0) return 'LOW';\n return 'UNKNOWN';\n }\n\n /** Extracts affected version range for a specific package */\n private extractAffectedRange(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const introduced = range.events.find((e) => e.introduced)?.introduced;\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (introduced && fixed) return `>=${introduced}, <${fixed}`;\n if (introduced) return `>=${introduced}`;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Extracts the fixed version for a specific package */\n private extractFixedVersion(vuln: OsvVulnerability, packageName: string): string | undefined {\n if (!vuln.affected) return undefined;\n\n for (const affected of vuln.affected) {\n if (affected.package?.name === packageName && affected.ranges) {\n for (const range of affected.ranges) {\n if (range.events) {\n const fixed = range.events.find((e) => e.fixed)?.fixed;\n if (fixed) return fixed;\n }\n }\n }\n }\n return undefined;\n }\n\n /** Maps our ecosystem names to OSV ecosystem names */\n private mapEcosystem(ecosystem: string): string {\n const map: Record<string, string> = {\n npm: 'npm',\n nuget: 'NuGet',\n cargo: 'crates.io',\n maven: 'Maven',\n pip: 'PyPI',\n go: 'Go',\n };\n return map[ecosystem] ?? ecosystem;\n }\n}\n\n// ─── OSV API Response Types ─────────────────────────────────────\n\ninterface OsvBatchResponse {\n results: Array<{\n vulns?: OsvVulnerability[];\n }>;\n}\n\ninterface OsvVulnerability {\n id: string;\n summary?: string;\n details?: string;\n aliases?: string[];\n published?: string;\n modified?: string;\n severity?: Array<{\n type: string;\n score: string;\n }>;\n affected?: Array<{\n package?: {\n name: string;\n ecosystem: string;\n };\n ranges?: Array<{\n type: string;\n events: Array<{\n introduced?: string;\n fixed?: string;\n last_affected?: string;\n }>;\n }>;\n versions?: string[];\n }>;\n database_specific?: {\n severity?: string;\n [key: string]: unknown;\n };\n references?: Array<{\n type: string;\n url: string;\n }>;\n}\n","import type { CveSource } from './source.interface.js';\nimport type { Dependency, CveCheckResult, Vulnerability, VulnerabilitySource } from '../core/types.js';\nimport { OsvSource } from './osv.js';\n\n/**\n * Aggregates vulnerability data from multiple CVE sources.\n * Deduplicates results by CVE ID across sources.\n */\nexport class CveAggregator {\n private sources: CveSource[];\n\n constructor(sources?: CveSource[]) {\n this.sources = sources ?? [\n new OsvSource(),\n // Future: new NvdSource(), new EuvdSource(), new CisaKevSource()\n ];\n }\n\n /**\n * Checks dependencies against all registered CVE sources.\n * Runs sources in parallel and merges/deduplicates results.\n */\n async check(dependencies: Dependency[]): Promise<CveCheckResult> {\n const startTime = Date.now();\n const sourcesQueried: VulnerabilitySource[] = [];\n const sourceErrors: { source: VulnerabilitySource; error: string }[] = [];\n const allVulns: Vulnerability[] = [];\n\n // Run all sources in parallel\n const results = await Promise.allSettled(\n this.sources.map(async (source) => {\n const vulns = await source.checkDependencies(dependencies);\n return { sourceId: source.sourceId, vulns };\n })\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled') {\n sourcesQueried.push(result.value.sourceId);\n allVulns.push(...result.value.vulns);\n } else {\n // Extract the source ID from the error context\n const sourceIndex = results.indexOf(result);\n const sourceId = this.sources[sourceIndex].sourceId;\n sourceErrors.push({\n source: sourceId,\n error: result.reason instanceof Error ? result.reason.message : String(result.reason),\n });\n }\n }\n\n // Deduplicate by CVE ID (prefer the entry with more data)\n const deduplicated = this.deduplicateVulnerabilities(allVulns);\n\n return {\n vulnerabilities: deduplicated,\n sourcesQueried,\n sourceErrors,\n checkDurationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Deduplicates vulnerabilities by ID.\n * When the same CVE appears from multiple sources,\n * keeps the one with more complete data (has CVSS score, has fix version, etc.)\n */\n private deduplicateVulnerabilities(vulns: Vulnerability[]): Vulnerability[] {\n const byKey = new Map<string, Vulnerability>();\n\n for (const vuln of vulns) {\n // Key by (vulnerability ID + package name) to handle the same CVE\n // affecting multiple packages\n const key = `${vuln.id}::${vuln.packageName}`;\n const existing = byKey.get(key);\n\n if (!existing) {\n byKey.set(key, vuln);\n } else {\n // Keep the one with more data\n byKey.set(key, this.pickBetterEntry(existing, vuln));\n }\n }\n\n return Array.from(byKey.values());\n }\n\n /** Picks the vulnerability entry with more complete data */\n private pickBetterEntry(a: Vulnerability, b: Vulnerability): Vulnerability {\n let scoreA = 0;\n let scoreB = 0;\n\n if (a.cvssScore !== undefined) scoreA++;\n if (b.cvssScore !== undefined) scoreB++;\n if (a.fixedVersion) scoreA++;\n if (b.fixedVersion) scoreB++;\n if (a.affectedVersionRange) scoreA++;\n if (b.affectedVersionRange) scoreB++;\n if (a.severity !== 'UNKNOWN') scoreA++;\n if (b.severity !== 'UNKNOWN') scoreB++;\n\n // Merge: start with the lesser entry, overlay with the better one.\n // Strip undefined/null values so they don't overwrite real data.\n const strip = (obj: Record<string, unknown>) =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null));\n\n const winner = scoreB > scoreA\n ? { ...strip(a as unknown as Record<string, unknown>), ...strip(b as unknown as Record<string, unknown>) } as unknown as Vulnerability\n : { ...strip(b as unknown as Record<string, unknown>), ...strip(a as unknown as Record<string, unknown>) } as unknown as Vulnerability;\n\n // Merge aliases\n const allAliases = new Set([...a.aliases, ...b.aliases]);\n winner.aliases = Array.from(allAliases);\n\n // If either says exploited, it's exploited\n winner.exploitedInWild = a.exploitedInWild || b.exploitedInWild;\n\n return winner;\n }\n}\n","import type { Reporter } from './reporter.interface.js';\nimport type { VerimuReport, Vulnerability, Severity } from '../core/types.js';\n\n/** Outputs a human-readable console report */\nexport class ConsoleReporter implements Reporter {\n readonly name = 'console';\n\n report(result: VerimuReport): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('┌─────────────────────────────────────────────┐');\n lines.push('│ VERIMU CRA COMPLIANCE SCAN │');\n lines.push('└─────────────────────────────────────────────┘');\n lines.push('');\n\n // Project info\n lines.push(` Project: ${result.project.path}`);\n lines.push(` Ecosystem: ${result.project.ecosystem}`);\n lines.push(` Dependencies: ${result.project.dependencyCount}`);\n lines.push(` Scanned at: ${result.generatedAt}`);\n lines.push('');\n\n // SBOM info\n lines.push(` ✓ SBOM generated (${result.sbom.format}, ${result.sbom.specVersion})`);\n lines.push(` Components: ${result.sbom.componentCount}`);\n lines.push('');\n\n // CVE results\n const vulns = result.cveCheck.vulnerabilities;\n if (vulns.length === 0) {\n lines.push(' ✓ No known vulnerabilities found');\n } else {\n lines.push(` ⚠ ${vulns.length} vulnerabilit${vulns.length === 1 ? 'y' : 'ies'} found:`);\n lines.push('');\n\n // Sort by severity: CRITICAL → HIGH → MEDIUM → LOW → UNKNOWN\n const sorted = [...vulns].sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));\n\n for (const vuln of sorted) {\n const badge = severityBadge(vuln.severity);\n const fix = vuln.fixedVersion ? ` → fix: ${vuln.fixedVersion}` : '';\n lines.push(` ${badge} ${vuln.id}`);\n lines.push(` ${vuln.packageName}@${vuln.affectedVersionRange ?? '?'}${fix}`);\n lines.push(` ${vuln.summary.slice(0, 100)}`);\n if (vuln.exploitedInWild) {\n lines.push(` 🔴 ACTIVELY EXPLOITED — 24h CRA reporting required`);\n }\n lines.push('');\n }\n }\n\n // Sources\n const sources = result.cveCheck.sourcesQueried.join(', ');\n lines.push(` Sources queried: ${sources} (${result.cveCheck.checkDurationMs}ms)`);\n\n if (result.cveCheck.sourceErrors.length > 0) {\n for (const err of result.cveCheck.sourceErrors) {\n lines.push(` ⚠ ${err.source}: ${err.error}`);\n }\n }\n\n // Summary\n lines.push('');\n lines.push(' ─── Summary ───');\n lines.push(` Total: ${result.summary.totalVulnerabilities} | ` +\n `Critical: ${result.summary.critical} | ` +\n `High: ${result.summary.high} | ` +\n `Medium: ${result.summary.medium} | ` +\n `Low: ${result.summary.low}`);\n\n if (result.summary.exploitedInWild > 0) {\n lines.push(` 🔴 ${result.summary.exploitedInWild} actively exploited — immediate action required`);\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n}\n\nfunction severityOrder(s: Severity): number {\n const order: Record<Severity, number> = {\n CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, UNKNOWN: 4,\n };\n return order[s] ?? 5;\n}\n\nfunction severityBadge(s: Severity): string {\n const badges: Record<Severity, string> = {\n CRITICAL: '[CRIT]',\n HIGH: '[HIGH]',\n MEDIUM: '[MED] ',\n LOW: '[LOW] ',\n UNKNOWN: '[???] ',\n };\n return badges[s] ?? '[???] ';\n}\n"],"mappings":"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","names":["randomUUID","severityOrder"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verimu",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "CRA compliance automation
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CRA compliance automation - SBOM generation, CVE monitoring, and vulnerability reporting for the EU Cyber Resilience Act.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"test": "vitest run",
|
|
32
32
|
"test:watch": "vitest",
|
|
33
33
|
"lint": "tsc --noEmit",
|
|
34
|
-
"prepublishOnly": "npm run build:check && npm run test && npm run build"
|
|
34
|
+
"prepublishOnly": "npm run build:check && npm run test && npm run build",
|
|
35
|
+
"type-check": "tsc --noEmit"
|
|
35
36
|
},
|
|
36
37
|
"keywords": [
|
|
37
38
|
"cra",
|
|
@@ -47,12 +48,11 @@
|
|
|
47
48
|
"supply-chain"
|
|
48
49
|
],
|
|
49
50
|
"author": "Verimu",
|
|
50
|
-
"license": "MIT",
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/
|
|
53
|
+
"url": "https://github.com/FullStackCraft/verimu"
|
|
54
54
|
},
|
|
55
|
-
"homepage": "https://verimu.
|
|
55
|
+
"homepage": "https://verimu.com",
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=20"
|
|
58
58
|
},
|