/** * Vulnerability scanning of an already-generated SBOM (#626, epic #551 * supply-chain follow-up to the SBOM stack #606/#613/#614/#609/#610). An SBOM * says *what's in* an artifact; a scanner says *which of those have known * CVEs*. This module produces findings; ./vex.ts suppresses the ones that * don't matter and ./vuln-gate.ts fails the deploy on the ones that do. * * Prefer scanning the **SBOM** (already generated + persisted, keyed by * digest) over re-scanning the image: deterministic (same SBOM -> same * findings), fast, and reuses work already done. Real backends shell out to * `grype`/`trivy` through the injectable `ProcessRunner` (./process-runner.ts), * exactly like ./tool-sbom-generator.ts's deep-scan backend — tests inject an * inline fake `VulnScanner` (an object literal with a canned `scan()`, see * ./vuln-gate.test.ts) and never invoke a real scanner, network, or vuln DB. * The scanner's vuln DB currency is the tool's job, not chant's. */ import type { Capability } from "../capability.js"; import type { SbomDocument } from "./sbom-generator.js"; import { type ProcessRunner } from "./process-runner.js"; /** CVE severity, highest to lowest. `unknown` sorts lowest so it never trips a threshold gate by accident. */ export type Severity = "critical" | "high" | "medium" | "low" | "negligible" | "unknown"; /** Severity rank (higher = more severe) so a gate can ask "severity >= critical". */ export declare const SEVERITY_RANK: Record; /** Normalize a scanner's free-form severity string ("High", "CRITICAL", "Negligible", …) to a `Severity`. Unrecognized -> `unknown`. */ export declare function normalizeSeverity(raw: string | undefined): Severity; /** One known vulnerability affecting one package in the scanned artifact. */ export interface VulnFinding { /** Advisory id, e.g. `"CVE-2024-12345"` or `"GHSA-…"` — the join key with VEX statements (./vex.ts). */ cveId: string; severity: Severity; /** Affected package name. */ package: string; installedVersion: string; /** First version that fixes it, when the scanner reports one. */ fixedVersion?: string; /** True when a fix exists (upgradeable) — the beginner-safe default gate blocks only fixable findings, since an unfixable one can't be actioned by bumping. */ fixable: boolean; /** EPSS score (0.0–1.0): probability of exploitation in the next 30 days. Absent when the scanner did not report one. */ epss?: number; /** EPSS percentile (0.0–1.0) — rank against all scored CVEs. */ epssPercentile?: number; /** Present in CISA's Known Exploited Vulnerabilities catalog. `undefined` means the scanner did not report KEV membership at all — NOT the same conclusion as a reported `false`. Never default this. */ inKev?: boolean; /** When the CVE entered the KEV catalog (ISO date, as the source reports it). */ kevDateAdded?: string; /** KEV remediation due date (ISO date). */ kevDueDate?: string; /** Known use in a ransomware campaign, per KEV. */ kevRansomware?: boolean; } export interface ScanInput { /** The SBOM to scan (the artifact's already-generated SPDX/CycloneDX doc). */ sbom: SbomDocument; /** Artifact digest the SBOM belongs to — informational, for logging/keying. */ digest?: string; } /** * Injectable vulnerability-scan boundary — the scan-side analogue of * `SbomGenerator` (./sbom-generator.ts) and `CloudExecutor` * (./cloud-executor.ts). A real implementation shells out to `grype`/`trivy`; * tests substitute an inline fake (an object literal with a canned `scan()`) * and never touch a real tool, network, or vuln DB. */ export interface VulnScanner { /** Scan an SBOM, returning every known vulnerability it surfaces. */ scan(input: ScanInput): Promise; } /** Which real CLI scanner a `ProcessRunner`-backed scanner shells out to. */ export type ScannerTool = "grype" | "trivy"; /** Parse `grype -o json` stdout into findings. Exported for tests to assert parsing without a live tool. */ export declare function parseGrypeOutput(stdout: string): VulnFinding[]; /** Parse `trivy sbom --format json` stdout into findings. */ export declare function parseTrivyOutput(stdout: string): VulnFinding[]; /** * A real `VulnScanner` that writes the SBOM to a temp file and scans it with * `grype` (default) or `trivy`, through the injectable `ProcessRunner`. * `requireTool` throws `ToolNotAvailableError` if the scanner is absent — a * missing scanner is a hard stop (like ./verify.ts's missing `cosign`), never * a silent "no vulns found," because that would let an unscanned artifact * through a gate. Never used in tests. */ export declare function createToolVulnScanner(tool?: ScannerTool, processRunner?: ProcessRunner): VulnScanner; /** * Thrown by `notImplementedVulnScanner` — the "no real scanner wired yet" * signal, mirroring `SbomGeneratorNotImplementedError` (./sbom-generator.ts). * The default is loud-and-specific rather than a silent empty scan, so a * caller that forgets to inject a scanner (or a mock in tests) fails obviously * instead of appearing to find zero vulnerabilities. */ export declare class VulnScannerNotImplementedError extends Error { constructor(); } /** Kept for tests and for a caller that wants the old loud "no scanner wired" behavior; no longer the registered default (#634). */ export declare const notImplementedVulnScanner: VulnScanner; /** * A `VulnScanner` that picks a real backend at scan time — `grype` if present, * else `trivy`. Unlike SBOM generation there is no hermetic fallback (a scan * needs a real vuln DB), so when neither tool is on `PATH` this throws a * `ToolNotAvailableError` naming what to install — an error a config-only user * can act on — rather than `VulnScannerNotImplementedError`, which told them to * edit code. This is the vuln-side analog of #630's hermetic-by-default SBOM * generator: the registered `scan-vulnerabilities`/`vuln-gate` capabilities now * work the moment a scanner is installed, with no code wiring. */ export declare function autoDetectVulnScanner(processRunner?: ProcessRunner): VulnScanner; /** The default `VulnScanner` the `scan-vulnerabilities`/`vuln-gate` capabilities fall back to when none is supplied: auto-detects `grype`/`trivy` and throws `ToolNotAvailableError` if neither is installed (#634). */ export declare function defaultVulnScanner(): VulnScanner; export interface ScanVulnerabilitiesInput { /** The SBOM to scan. */ sbom: SbomDocument; /** Artifact digest the SBOM belongs to (recorded on the output). */ digest?: string; } export interface ScanVulnerabilitiesOutput { findings: VulnFinding[]; /** Echoed back so a downstream `vuln-gate` step can key results to the artifact. */ digest?: string; } /** * `scan-vulnerabilities` capability — scan an SBOM for known CVEs via the * injectable `VulnScanner`. Produces findings only; suppression (VEX) and the * pass/fail decision live in ./vuln-gate.ts, so a composition can scan once * and reuse the findings, or run the gate directly (which scans for you). */ export declare function createScanVulnerabilitiesCapability(scanner?: VulnScanner): Capability; /** Default `scan-vulnerabilities` capability, backed by the not-implemented scanner (inject a real/mock one). */ export declare const scanVulnerabilitiesCapability: Capability; //# sourceMappingURL=vuln-scan.d.ts.map