mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-27 18:57:09 +00:00
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:
@@ -46,6 +46,8 @@ import { ScanComparisonSheet } from './ScanComparisonSheet';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cveUrl } from '@/lib/cveUrl';
|
||||
import { SEVERITY_ROW_TINT } from '@/lib/severityStyles';
|
||||
import type {
|
||||
VulnerabilityScan,
|
||||
VulnerabilityDetail,
|
||||
@@ -88,7 +90,7 @@ function SeverityChip({ severity }: { severity: VulnSeverity }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] font-mono tabular-nums uppercase tracking-wide',
|
||||
'inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel',
|
||||
SEVERITY_CLASSES[severity],
|
||||
)}
|
||||
>
|
||||
@@ -372,10 +374,13 @@ export function VulnerabilityScanSheet({
|
||||
return (
|
||||
<Sheet open={scanId != null} onOpenChange={(open) => !open && onClose()}>
|
||||
<SheetContent className="sm:max-w-2xl flex flex-col p-0">
|
||||
<SheetHeader className="p-6 pb-4 border-b">
|
||||
<SheetTitle className="flex items-center gap-2 pr-6">
|
||||
<ShieldCheck className="w-4 h-4 text-muted-foreground" strokeWidth={1.5} />
|
||||
<span className="font-mono text-sm truncate">
|
||||
<SheetHeader className="px-6 pt-6 pb-4 pr-14 border-b border-border space-y-2">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
|
||||
Vulnerability scan · {scan?.triggered_by ?? '-'}
|
||||
</div>
|
||||
<SheetTitle className="flex items-center gap-2 font-display italic text-2xl">
|
||||
<ShieldCheck className="w-5 h-5 text-muted-foreground" strokeWidth={1.5} />
|
||||
<span className="font-mono text-base truncate not-italic">
|
||||
{scan?.image_ref ?? 'Loading...'}
|
||||
</span>
|
||||
</SheetTitle>
|
||||
@@ -396,29 +401,55 @@ export function VulnerabilityScanSheet({
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
{/* Summary stats */}
|
||||
<div className="px-6 py-4 border-b space-y-3">
|
||||
{scan.policy_evaluation?.violated && (
|
||||
<div
|
||||
role="alert"
|
||||
className="relative rounded border border-destructive/40 bg-destructive/10 px-3 py-2 pl-4 shadow-card-bevel"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="absolute left-0 top-0 bottom-0 w-[3px] bg-destructive/80 rounded-l"
|
||||
/>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-destructive">
|
||||
Policy violation
|
||||
</div>
|
||||
<div className="text-sm text-stat-value mt-0.5">
|
||||
<span className="font-mono">{scan.policy_evaluation.policyName}</span> blocks
|
||||
severities at or above{' '}
|
||||
<span className="font-mono tabular-nums">
|
||||
{scan.policy_evaluation.maxSeverity}
|
||||
</span>
|
||||
. This scan's highest severity is{' '}
|
||||
<span className="font-mono tabular-nums">
|
||||
{scan.highest_severity ?? 'UNKNOWN'}
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{scan.critical_count > 0 && (
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums', SEVERITY_CLASSES.CRITICAL)}>
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.CRITICAL)}>
|
||||
{scan.critical_count} CRITICAL
|
||||
</span>
|
||||
)}
|
||||
{scan.high_count > 0 && (
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums', SEVERITY_CLASSES.HIGH)}>
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.HIGH)}>
|
||||
{scan.high_count} HIGH
|
||||
</span>
|
||||
)}
|
||||
{scan.medium_count > 0 && (
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums', SEVERITY_CLASSES.MEDIUM)}>
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.MEDIUM)}>
|
||||
{scan.medium_count} MEDIUM
|
||||
</span>
|
||||
)}
|
||||
{scan.low_count > 0 && (
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums', SEVERITY_CLASSES.LOW)}>
|
||||
<span className={cn('rounded border px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel', SEVERITY_CLASSES.LOW)}>
|
||||
{scan.low_count} LOW
|
||||
</span>
|
||||
)}
|
||||
{scan.total_vulnerabilities === 0 && (
|
||||
<span className="rounded border border-success/40 bg-success/10 text-success px-2 py-1 text-xs font-mono tabular-nums uppercase">
|
||||
<span className="rounded border border-success/40 bg-success/10 text-success px-2 py-1 text-xs font-mono tabular-nums uppercase tracking-[0.18em] shadow-card-bevel">
|
||||
No vulnerabilities
|
||||
</span>
|
||||
)}
|
||||
@@ -426,20 +457,20 @@ export function VulnerabilityScanSheet({
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
|
||||
<div>
|
||||
<div className="text-stat-subtitle uppercase tracking-wide">Total</div>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Total</div>
|
||||
<div className="font-mono tabular-nums text-stat-value">{scan.total_vulnerabilities}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-stat-subtitle uppercase tracking-wide">Fixable</div>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Fixable</div>
|
||||
<div className="font-mono tabular-nums text-success">{scan.fixable_count}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-stat-subtitle uppercase tracking-wide">Triggered</div>
|
||||
<div className="font-mono text-stat-value">{scan.triggered_by}</div>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Triggered</div>
|
||||
<div className="font-mono tabular-nums text-stat-value">{scan.triggered_by}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-stat-subtitle uppercase tracking-wide">Scanned</div>
|
||||
<div className="font-mono text-stat-value">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">Scanned</div>
|
||||
<div className="font-mono tabular-nums text-stat-value">
|
||||
{new Date(scan.scanned_at).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -521,7 +552,7 @@ export function VulnerabilityScanSheet({
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-xs text-stat-subtitle uppercase tracking-wide">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
|
||||
Compare against
|
||||
</div>
|
||||
<Combobox
|
||||
@@ -635,18 +666,23 @@ export function VulnerabilityScanSheet({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[180px]">CVE</TableHead>
|
||||
<TableHead>Package</TableHead>
|
||||
<TableHead className="w-[100px]">Severity</TableHead>
|
||||
<TableHead className="w-[110px]">Installed</TableHead>
|
||||
<TableHead className="w-[110px]">Fixed</TableHead>
|
||||
<TableHead className="w-[180px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">CVE</TableHead>
|
||||
<TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Package</TableHead>
|
||||
<TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
|
||||
<TableHead className="w-[110px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Installed</TableHead>
|
||||
<TableHead className="w-[110px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Fixed</TableHead>
|
||||
{canManageSuppressions && <TableHead className="w-[40px]" />}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pageItems.map((d) => (
|
||||
<TableRow key={d.id} className={d.suppressed ? 'opacity-60' : undefined}>
|
||||
<TableCell className="font-mono text-xs">
|
||||
{pageItems.map((d) => {
|
||||
const href = cveUrl(d.vulnerability_id, d.primary_url);
|
||||
return (
|
||||
<TableRow
|
||||
key={d.id}
|
||||
className={cn(SEVERITY_ROW_TINT[d.severity], d.suppressed && 'opacity-60')}
|
||||
>
|
||||
<TableCell className="font-mono text-xs tabular-nums">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
{d.suppressed && (
|
||||
<ShieldOff
|
||||
@@ -655,9 +691,9 @@ export function VulnerabilityScanSheet({
|
||||
aria-label="Suppressed"
|
||||
/>
|
||||
)}
|
||||
{d.primary_url ? (
|
||||
{href ? (
|
||||
<a
|
||||
href={d.primary_url}
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="inline-flex items-center gap-1 hover:underline"
|
||||
@@ -706,7 +742,8 @@ export function VulnerabilityScanSheet({
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
@@ -757,15 +794,15 @@ export function VulnerabilityScanSheet({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Severity</TableHead>
|
||||
<TableHead className="w-[160px]">Rule</TableHead>
|
||||
<TableHead>Title</TableHead>
|
||||
<TableHead className="w-[260px]">Target</TableHead>
|
||||
<TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
|
||||
<TableHead className="w-[160px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Rule</TableHead>
|
||||
<TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Title</TableHead>
|
||||
<TableHead className="w-[260px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Target</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{secretsPageItems.map((s) => (
|
||||
<TableRow key={s.id}>
|
||||
<TableRow key={s.id} className={SEVERITY_ROW_TINT[s.severity]}>
|
||||
<TableCell>
|
||||
<SeverityChip severity={s.severity} />
|
||||
</TableCell>
|
||||
@@ -850,16 +887,16 @@ export function VulnerabilityScanSheet({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Severity</TableHead>
|
||||
<TableHead className="w-[140px]">Check</TableHead>
|
||||
<TableHead>Title</TableHead>
|
||||
<TableHead className="w-[200px]">Target</TableHead>
|
||||
<TableHead className="w-[220px]">Fix</TableHead>
|
||||
<TableHead className="w-[100px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Severity</TableHead>
|
||||
<TableHead className="w-[140px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Check</TableHead>
|
||||
<TableHead className="text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Title</TableHead>
|
||||
<TableHead className="w-[200px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Target</TableHead>
|
||||
<TableHead className="w-[220px] text-[10px] uppercase tracking-[0.18em] font-mono text-stat-subtitle">Fix</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{misconfigsPageItems.map((m) => (
|
||||
<TableRow key={m.id}>
|
||||
<TableRow key={m.id} className={SEVERITY_ROW_TINT[m.severity]}>
|
||||
<TableCell>
|
||||
<SeverityChip severity={m.severity} />
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user