feat(images): Trivy-powered vulnerability scanning (#635)

* feat(images): Trivy-powered vulnerability scanning

Scan container images for known CVEs via Trivy. On-demand scanning and
severity badges are available on every tier; scheduled scans, scan
policies, SBOM generation, and scan history are gated to Skipper+.

- New TrivyService (binary detection, per-image scan, SBOM, digest cache)
- Three new tables: vulnerability_scans, vulnerability_details, scan_policies
- 12 routes under /api/security (scan, results, summaries, SBOM, policies, compare)
- Post-deploy async scans wired into all five deploy paths, with a
  per-deploy opt-out toggle in the App Store deploy sheet
- "scan" action type added to SchedulerService for fleet-wide recurring scans
- Frontend: severity badges in Resources Hub with animated cursor detail,
  scan results drawer with vulnerability table and filters, and a new
  Security section in Settings for scan policy CRUD
- Policy threshold violations dispatch a warning or critical alert based on
  the policy's block_on_deploy flag; deploys themselves are never blocked

* fix(security): compute scan age in useEffect to satisfy react-hooks/purity
This commit is contained in:
Anso
2026-04-16 15:03:36 -04:00
committed by GitHub
parent 4c5aa73196
commit c9cd6990d2
23 changed files with 3452 additions and 18 deletions
+28
View File
@@ -0,0 +1,28 @@
import { useEffect, useState } from 'react';
import { apiFetch } from '@/lib/api';
import type { TrivyStatus } from '@/types/security';
export function useTrivyStatus(): TrivyStatus {
const [status, setStatus] = useState<TrivyStatus>({ available: false, version: null });
useEffect(() => {
let cancelled = false;
apiFetch('/security/trivy-status')
.then((r) => (r.ok ? r.json() : null))
.then((d) => {
if (cancelled || !d) return;
setStatus({
available: !!d.available,
version: typeof d.version === 'string' ? d.version : null,
});
})
.catch((err) => {
console.error('Failed to fetch Trivy status:', err);
});
return () => {
cancelled = true;
};
}, []);
return status;
}