Skip to content

Commit 6b87cb2

Browse files
authored
show ignored vulnerabilities on SBOM page (#126)
* setup astro LSP * load advisories from a file temporarily * update advisory file structure * show ignored vulns in a separate row * make ignored border yellow * restore fetching files
1 parent 4842530 commit 6b87cb2

File tree

3 files changed

+168
-72
lines changed

3 files changed

+168
-72
lines changed

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
packages = with pkgs; [
2121
nodejs_24
2222
pnpm
23-
# TS/JS LSP
24-
vtsls
23+
# astro LSP
24+
astro-language-server
2525
];
2626
};
2727
});

src/pages/sbom.astro

Lines changed: 163 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,20 @@ interface Vulnerability {
4646
PrimaryURL?: string;
4747
Title?: string;
4848
}
49-
interface AdvisoryResult { Vulnerabilities?: Vulnerability[] }
49+
interface ExperimentalModifiedFinding {
50+
Type?: string;
51+
Status?: string;
52+
Statement?: string;
53+
Finding?: Vulnerability;
54+
}
55+
interface AdvisoryResult {
56+
Vulnerabilities?: Vulnerability[];
57+
ExperimentalModifiedFindings: ExperimentalModifiedFinding[];
58+
}
5059
interface AdvisoryFile { CreatedAt?: string; Metadata?: { CreatedAt?: string }; Results?: AdvisoryResult[] }
5160
5261
const sboms = new Map<string, SbomEntry>();
53-
const advisories = new Map<string, { createdAt: string | null; vulns: Vulnerability[] }>();
62+
const advisories = new Map<string, { createdAt: string | null; vulns: Vulnerability[], ignores: ExperimentalModifiedFinding[] }>();
5463
5564
// Build entries and fetch advisories from GitHub
5665
await Promise.all(COMPONENTS.map(async (c) => {
@@ -62,7 +71,8 @@ await Promise.all(COMPONENTS.map(async (c) => {
6271
const json = (await res.json()) as AdvisoryFile;
6372
const createdAt = json.CreatedAt || json.Metadata?.CreatedAt || null;
6473
const vulns = (json.Results || []).flatMap((r: AdvisoryResult) => (r.Vulnerabilities || []));
65-
advisories.set(idKey, { createdAt, vulns });
74+
const ignores = (json.Results || []).flatMap((r: AdvisoryResult) => (r.ExperimentalModifiedFindings || []));
75+
advisories.set(idKey, { createdAt, vulns, ignores });
6676
}
6777
} catch {}
6878
}));
@@ -184,15 +194,20 @@ const tags = [
184194
const badgeClass = report
185195
? (report.status === 'ok' ? 'ok' : (severity || 'issues'))
186196
: 'na';
197+
198+
const adv = advisories.get(`${c.name}@${c.version}`);
199+
const vulns = adv?.vulns || [];
200+
const ignores = adv?.ignores || [];
201+
187202
const hasVulns = report && report.status !== 'ok';
188-
const rowId = `component-${index}`;
203+
const hasIgnores = ignores.length !== 0;
189204
const detailsId = `details-${index}`;
190205

191206
return (
192207
<>
193208
<tr>
194209
<td>
195-
{hasVulns ? (
210+
{(hasVulns || hasIgnores) ? (
196211
<button
197212
class="vuln-toggle"
198213
data-target={detailsId}
@@ -220,62 +235,126 @@ const tags = [
220235
<td><span class={`badge ${badgeClass}`}>{statusLabel}</span></td>
221236
<td>{hasVulns ? (c.status || 'Patch in progress') : ''}</td>
222237
</tr>
223-
{hasVulns && (
224-
<tr id={detailsId} class="vuln-details" style="display: none;">
225-
<td colspan="6">
226-
<div class="vuln-details-content">
227-
<h4>Vulnerability Details</h4>
228-
<div class="vuln-list">
229-
{(() => {
230-
const adv = advisories.get(`${c.name}@${c.version}`);
231-
const vulns = adv?.vulns || [];
232-
233-
if (vulns.length === 0) {
234-
return <p>Vulnerabilities detected but no specific details available.</p>;
235-
}
236-
237-
return (
238-
<div class="vuln-items">
239-
{vulns.slice(0, 10).map((vuln) => (
240-
<div class="vuln-item">
241-
<div class="vuln-header">
242-
<div class="vuln-cve">
243-
{vuln.PrimaryURL ? (
244-
<a href={vuln.PrimaryURL} target="_blank" rel="noopener noreferrer">
245-
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
246-
</a>
247-
) : (
248-
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
238+
{(hasVulns || hasIgnores) && (
239+
<>
240+
{hasVulns && (
241+
<tr id={`${detailsId}-vulns`} class="vuln-details" style="display: none;">
242+
<td colspan="6">
243+
<div class="vuln-details-content">
244+
<h4>Vulnerability Details</h4>
245+
<div class="vuln-list">
246+
{(() => {
247+
if (vulns.length === 0) {
248+
return <p>Vulnerabilities detected but no specific details available.</p>;
249+
}
250+
251+
return (
252+
<div class="vuln-items">
253+
{vulns.slice(0, 10).map((vuln) => (
254+
<div class="vuln-item">
255+
<div class="vuln-header">
256+
<div class="vuln-cve">
257+
{vuln.PrimaryURL ? (
258+
<a href={vuln.PrimaryURL} target="_blank" rel="noopener noreferrer">
259+
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
260+
</a>
261+
) : (
262+
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
263+
)}
264+
<span class={`severity-badge ${(vuln.Severity || 'unknown').toLowerCase()}`}>
265+
{vuln.Severity || 'Unknown'}
266+
</span>
267+
</div>
268+
{vuln.PkgID && (
269+
<div class="vuln-package">
270+
Package: <code>{vuln.PkgID}</code>
271+
</div>
272+
)}
273+
</div>
274+
{vuln.Title && (
275+
<div class="vuln-title">
276+
{vuln.Title}
277+
</div>
249278
)}
250-
<span class={`severity-badge ${(vuln.Severity || 'unknown').toLowerCase()}`}>
251-
{vuln.Severity || 'Unknown'}
252-
</span>
253279
</div>
254-
{vuln.PkgID && (
255-
<div class="vuln-package">
256-
Package: <code>{vuln.PkgID}</code>
257-
</div>
258-
)}
259-
</div>
260-
{vuln.Title && (
261-
<div class="vuln-title">
262-
{vuln.Title}
280+
))}
281+
{vulns.length > 10 && (
282+
<div class="vuln-more">
283+
... and {vulns.length - 10} more vulnerabilities
263284
</div>
264285
)}
265286
</div>
266-
))}
267-
{vulns.length > 10 && (
268-
<div class="vuln-more">
269-
... and {vulns.length - 10} more vulnerabilities
287+
);
288+
})()}
289+
</div>
290+
</div>
291+
</td>
292+
</tr>
293+
)}
294+
{hasIgnores && (
295+
<tr id={`${detailsId}-ignores`} class="vuln-details" style="display: none;">
296+
<td colspan="6">
297+
<div class="vuln-details-content ignored">
298+
<h4>Ignored Vulnerability Details</h4>
299+
<div class="vuln-list">
300+
{(() => {
301+
if (ignores.length === 0) {
302+
return <p>Ignored vulnerabilities detected but no specific details available.</p>;
303+
}
304+
305+
return (
306+
<div class="vuln-items">
307+
{ignores.slice(0, 10).map((ignore) => {
308+
const vuln = ignore.Finding;
309+
if (!vuln) return null;
310+
return (
311+
<div class="vuln-item">
312+
<div class="vuln-header">
313+
<div class="vuln-cve">
314+
{vuln.PrimaryURL ? (
315+
<a href={vuln.PrimaryURL} target="_blank" rel="noopener noreferrer">
316+
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
317+
</a>
318+
) : (
319+
<strong>{vuln.VulnerabilityID || 'Unknown CVE'}</strong>
320+
)}
321+
<span class={`severity-badge ${(vuln.Severity || 'unknown').toLowerCase()}`}>
322+
{vuln.Severity || 'Unknown'}
323+
</span>
324+
</div>
325+
{vuln.PkgID && (
326+
<div class="vuln-package">
327+
Package: <code>{vuln.PkgID}</code>
328+
</div>
329+
)}
330+
</div>
331+
{vuln.Title && (
332+
<div class="vuln-title">
333+
{vuln.Title}
334+
</div>
335+
)}
336+
{ignore.Statement && (
337+
<div class="ignore-statement">
338+
<strong>Ignore Reason:</strong> {ignore.Statement}
339+
</div>
340+
)}
341+
</div>
342+
);
343+
})}
344+
{ignores.length > 10 && (
345+
<div class="vuln-more">
346+
... and {ignores.length - 10} more ignored vulnerabilities
347+
</div>
348+
)}
270349
</div>
271-
)}
272-
</div>
273-
);
274-
})()}
275-
</div>
276-
</div>
277-
</td>
278-
</tr>
350+
);
351+
})()}
352+
</div>
353+
</div>
354+
</td>
355+
</tr>
356+
)}
357+
</>
279358
)}
280359
</>
281360
);
@@ -418,6 +497,9 @@ const tags = [
418497
padding: 16px;
419498
border-left: 3px solid #dc3545;
420499
}
500+
.vuln-details-content.ignored {
501+
border-left-color: #ffc107;
502+
}
421503
.vuln-details-content h4 {
422504
margin: 0 0 12px 0;
423505
font-size: 14px;
@@ -473,6 +555,15 @@ const tags = [
473555
color: #333;
474556
line-height: 1.4;
475557
}
558+
.ignore-statement {
559+
margin-top: 8px;
560+
padding: 8px;
561+
background: #f8f9fa;
562+
border-radius: 4px;
563+
font-size: 12px;
564+
color: #666;
565+
border-left: 3px solid #6c757d;
566+
}
476567
.vuln-more {
477568
padding: 8px 12px;
478569
background: #f8f9fa;
@@ -512,27 +603,32 @@ const tags = [
512603
</style>
513604

514605
<script>
515-
// Handle vulnerability details toggle
606+
// Handle vulnerability details toggle for multiple rows
516607
document.addEventListener('DOMContentLoaded', function() {
517608
const toggleButtons = document.querySelectorAll('.vuln-toggle');
518609

519610
toggleButtons.forEach((button) => {
520-
button.addEventListener('click', function(this: HTMLElement) {
611+
button.addEventListener('click', function() {
521612
const targetId = this.getAttribute('data-target');
522613
if (!targetId) return;
523-
const targetRow = document.getElementById(targetId);
614+
615+
// Find all related detail rows (vulnerabilities and ignores)
616+
const vulnRow = document.getElementById(`${targetId}-vulns`);
617+
const ignoreRow = document.getElementById(`${targetId}-ignores`);
618+
524619
const isExpanded = this.getAttribute('aria-expanded') === 'true';
525620

526-
if (targetRow) {
527-
if (isExpanded) {
528-
targetRow.style.display = 'none';
529-
this.setAttribute('aria-expanded', 'false');
530-
} else {
531-
targetRow.style.display = 'table-row';
532-
this.setAttribute('aria-expanded', 'true');
533-
}
621+
// Toggle visibility of all related rows
622+
if (vulnRow) {
623+
vulnRow.style.display = isExpanded ? 'none' : 'table-row';
534624
}
625+
if (ignoreRow) {
626+
ignoreRow.style.display = isExpanded ? 'none' : 'table-row';
627+
}
628+
629+
// Update button state
630+
this.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
535631
});
536632
});
537633
});
538-
</script>
634+
</script>

0 commit comments

Comments
 (0)