fix(security): harden Trivy scan lifecycle, logging, and docs (#639)

* fix(security): harden Trivy scan lifecycle, logging, and docs

- Call TrivyService.initialize() at startup so capability state is
  accurate before first request; add periodic re-detect to the scheduler
  so newly installed Trivy binaries light up without a restart.
- Add markStaleScansAsFailed sweep (+ idx_vuln_scans_status index) to
  recover any scan row left in_progress after a crash or timeout; sweep
  runs before the paid-tier gate so every tier self-heals.
- Split scanImage persistence into beginScan/finishScan so the manual
  scan route owns a single code path and can return a scanId synchronously
  while work continues asynchronously.
- Validate image refs on /api/security/scan and /sbom via new utility;
  defense-in-depth against shell-metacharacter payloads.
- Dispatch a warning-level alert when a post-deploy scan fails so the
  operator has a user-visible path to the failure instead of a silent log.
- Share DIGEST_CACHE_TTL_MS and severity ordering across service and
  route layers; remove dead invalidateDetection().
- Add [Trivy:diag] logging gated behind developer_mode for support
  diagnostics; production logs unchanged.
- Frontend: defensive toast fallback chain, sr-only SheetDescription,
  and a truncation badge when the 500-item detail fetch is capped.
- Tests: extend trivy-service and vulnerability-db suites; add
  image-ref and severity unit tests.
- Docs: expand vulnerability-scanning troubleshooting with recovery,
  re-detect, and diagnostic-log guidance; link Dockerfile comment to
  trivy-setup.

* fix(security): drop unnecessary escape in image-ref forbidden-char regex
This commit is contained in:
Anso
2026-04-16 20:32:38 -04:00
committed by GitHub
parent f8eb1b4e88
commit dc8370f5a4
16 changed files with 563 additions and 140 deletions
+15
View File
@@ -582,6 +582,7 @@ export class DatabaseService {
CREATE INDEX IF NOT EXISTS idx_vuln_scans_node_image ON vulnerability_scans(node_id, image_ref);
CREATE INDEX IF NOT EXISTS idx_vuln_scans_digest ON vulnerability_scans(image_digest);
CREATE INDEX IF NOT EXISTS idx_vuln_scans_scanned_at ON vulnerability_scans(scanned_at);
CREATE INDEX IF NOT EXISTS idx_vuln_scans_status ON vulnerability_scans(status);
CREATE TABLE IF NOT EXISTS vulnerability_details (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2153,6 +2154,20 @@ export class DatabaseService {
return result.changes;
}
public markStaleScansAsFailed(olderThanMs: number): number {
const cutoff = Date.now() - olderThanMs;
const result = this.db
.prepare(
`UPDATE vulnerability_scans
SET status = 'failed',
error = 'Scan did not complete within expected time',
scan_duration_ms = ? - scanned_at
WHERE status = 'in_progress' AND scanned_at < ?`,
)
.run(Date.now(), cutoff);
return result.changes;
}
public isImageBeingScanned(nodeId: number, imageRef: string): boolean {
const row = this.db
.prepare(