mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 18:56:53 +00:00
feat(security): export scan results as SARIF 2.1.0 (#652)
Adds a new SarifExporter service that builds a SARIF 2.1.0 document from the stored scan findings (vulnerabilities, secrets, misconfigs). Rule IDs are namespaced to avoid collisions in a flat result list. Suppressions carry through as SARIF suppressions[] entries so GitHub code scanning and Defender for Cloud see the same accepted status shown in the UI. Exposed via GET /api/security/scans/:id/sarif, admin + paid-tier gated to match the SBOM export precedent. A SARIF button appears in the scan sheet next to SBOM and CSV for paid tiers.
This commit is contained in:
@@ -345,6 +345,30 @@ export function VulnerabilityScanSheet({
|
||||
URL.revokeObjectURL(url);
|
||||
}, [scan, details]);
|
||||
|
||||
const exportSarif = useCallback(async () => {
|
||||
if (!scan) return;
|
||||
try {
|
||||
const res = await apiFetch(`/security/scans/${scan.id}/sarif`);
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body?.error || 'Failed to generate SARIF');
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${scan.image_ref.replace(/[^a-z0-9]+/gi, '_')}.sarif.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success('SARIF downloaded');
|
||||
} catch (err) {
|
||||
const error = err as { message?: string; error?: string; data?: { error?: string } };
|
||||
toast.error(error?.message || error?.error || error?.data?.error || 'SARIF export failed');
|
||||
}
|
||||
}, [scan]);
|
||||
|
||||
return (
|
||||
<Sheet open={scanId != null} onOpenChange={(open) => !open && onClose()}>
|
||||
<SheetContent className="sm:max-w-2xl flex flex-col p-0">
|
||||
@@ -459,6 +483,18 @@ export function VulnerabilityScanSheet({
|
||||
<Download className="w-3.5 h-3.5 mr-1.5" strokeWidth={1.5} />
|
||||
CSV
|
||||
</Button>
|
||||
{canGenerateSbom && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={exportSarif}
|
||||
disabled={scan.status !== 'completed'}
|
||||
title="Export findings as SARIF 2.1.0 for GitHub code scanning"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 mr-1.5" strokeWidth={1.5} />
|
||||
SARIF
|
||||
</Button>
|
||||
)}
|
||||
{canCompare && (
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
Reference in New Issue
Block a user