@@ -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+ }
5059interface AdvisoryFile { CreatedAt? : string ; Metadata? : { CreatedAt? : string }; Results? : AdvisoryResult [] }
5160
5261const 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
5665await 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