feat(security): polish scan sheets, fix CVE links, surface policy violations (#721)

* feat(security): polish scan sheets, fix CVE links, surface policy violations

Adds cveUrl helper that rewrites Trivy's 404-ing avd.aquasec.com links to
cve.org for CVE-prefixed IDs (GHSA and misconfig URLs pass through unchanged).
Redesigns both scan sheets with shadow-card-bevel chips, tracked-mono kickers,
severity row tinting with a left accent rail, and tabular-nums timestamps.
Surfaces a destructive policy-violation banner on scans whose policy_evaluation
row flags a block, and fixes the compare sheet's delta ribbon so CRITICAL
net-positive deltas render in destructive (not warning) tone. Backend parses
the JSON policy_evaluation column at the API boundary so the UI receives a
structured object.

* chore(security): suppress CVE-2026-32281 and CVE-2026-32283 in Trivy scan

Both CVEs affect Go stdlib crypto/x509 and TLS in Docker CLI 29.4.0
(Go 1.26.1) and Compose v5.1.2 (Go 1.25.8). No upstream static binary
has been released with the patched Go 1.26.2 or 1.25.9 runtimes yet.

Exposure analysis: the Docker CLI and compose plugin connect to the local
Docker socket (Unix socket, no TLS) and to public registries with well-known
CAs. Neither CVE is exploitable in this configuration. Added alongside
sibling entries already in .trivyignore for the same binary versions.

Revisit on next Docker CLI and Compose upstream release.
This commit is contained in:
Anso
2026-04-21 08:51:35 -04:00
committed by GitHub
parent e4fdb1cd6c
commit 12c2b37510
12 changed files with 295 additions and 83 deletions
+50
View File
@@ -0,0 +1,50 @@
import { describe, it, expect } from 'vitest';
import { cveUrl } from '../cveUrl';
describe('cveUrl', () => {
it('rewrites uppercase CVE IDs to cve.org', () => {
expect(cveUrl('CVE-2024-1234')).toBe(
'https://www.cve.org/CVERecord?id=CVE-2024-1234',
);
});
it('rewrites lowercase CVE IDs uppercased', () => {
expect(cveUrl('cve-2024-1234')).toBe(
'https://www.cve.org/CVERecord?id=CVE-2024-1234',
);
});
it('trims surrounding whitespace before rewriting', () => {
expect(cveUrl(' CVE-2025-9999 ')).toBe(
'https://www.cve.org/CVERecord?id=CVE-2025-9999',
);
});
it('returns the fallback for GHSA advisory IDs', () => {
const ghsa = 'https://github.com/advisories/GHSA-xxxx-yyyy-zzzz';
expect(cveUrl('GHSA-xxxx-yyyy-zzzz', ghsa)).toBe(ghsa);
});
it('returns the fallback for AVD misconfig IDs', () => {
const avd = 'https://avd.aquasec.com/misconfig/ds002';
expect(cveUrl('AVD-DS-0002', avd)).toBe(avd);
});
it('returns the fallback when id is null', () => {
expect(cveUrl(null, 'https://example.test/advisory')).toBe(
'https://example.test/advisory',
);
});
it('returns null when id is undefined and no fallback', () => {
expect(cveUrl(undefined)).toBeNull();
});
it('returns null when id is empty and no fallback', () => {
expect(cveUrl('')).toBeNull();
});
it('returns null when id is empty and fallback is null', () => {
expect(cveUrl('', null)).toBeNull();
});
});
+18
View File
@@ -0,0 +1,18 @@
const CVE_PATTERN = /^cve-\d{4}-\d+$/i;
/**
* Trivy's PrimaryURL is usually https://avd.aquasec.com/nvd/<id>, which 404s.
* For CVE-prefixed IDs we rewrite to cve.org. GHSA, AVD-misconfig, and other
* identifiers keep the Trivy-supplied fallback.
*/
export function cveUrl(
id: string | null | undefined,
fallback?: string | null,
): string | null {
if (!id) return fallback ?? null;
const trimmed = id.trim();
if (CVE_PATTERN.test(trimmed)) {
return `https://www.cve.org/CVERecord?id=${trimmed.toUpperCase()}`;
}
return fallback ?? null;
}
+9
View File
@@ -0,0 +1,9 @@
import type { VulnSeverity } from '@/types/security';
export const SEVERITY_ROW_TINT: Record<VulnSeverity, string> = {
CRITICAL: 'bg-destructive/10 border-l-[3px] border-destructive/70',
HIGH: 'bg-warning/10 border-l-[3px] border-warning/70',
MEDIUM: 'border-l-[3px] border-info/40',
LOW: 'border-l-[3px] border-transparent',
UNKNOWN: 'border-l-[3px] border-transparent',
};