251251 </div >
252252 </div >
253253
254- <!-- Supply Chain Audit (Pass 2) -->
254+ <!-- Supply Chain Audit -->
255+ <!-- Pass 2 in-progress banner (shown independently of existing findings) -->
255256 <div v-if =" report.pass2_running" class =" alert alert-info" >
256257 <span class =" loading loading-spinner loading-sm" ></span >
257258 <div >
258259 <h3 class =" font-bold" >Supply Chain Audit</h3 >
259- <p class =" text-sm" >Deep dependency analysis running in background. Results will appear here when complete.</p >
260+ <p class =" text-sm" >Deep dependency analysis running in background. Additional CVEs will appear here when complete.</p >
260261 </div >
261262 </div >
262- <div v-else-if =" report.pass2_complete && pass2Findings.length > 0" class =" space-y-4" >
263+ <!-- CVE/package findings from any pass (flagged by backend) -->
264+ <div v-if =" supplyChainFindings.length > 0" class =" space-y-4" >
263265 <div class =" collapse collapse-arrow bg-base-100 shadow-md" >
264266 <input type =" checkbox" />
265267 <div class =" collapse-title font-medium flex items-center gap-2" >
266268 <span >Supply Chain Audit (CVEs)</span >
267- <span class =" badge badge-sm" :class =" pass2HasDangerous ? 'badge-error' : pass2HasWarnings ? 'badge-warning' : 'badge-info'" >{{ pass2Findings .length }}</span >
269+ <span class =" badge badge-sm" :class =" supplyChainHasDangerous ? 'badge-error' : supplyChainHasWarnings ? 'badge-warning' : 'badge-info'" >{{ supplyChainFindings .length }}</span >
268270 </div >
269271 <div class =" collapse-content" >
270272 <div class =" space-y-2" >
271- <div v-for =" (finding, idx) in pass2Findings " :key =" 'p2 -' + idx"
273+ <div v-for =" (finding, idx) in supplyChainFindings " :key =" 'sc -' + idx"
272274 class =" collapse collapse-arrow bg-base-200 rounded-lg"
273275 >
274276 <input type =" checkbox" />
344346 </div >
345347 </div >
346348 </div >
347- <div v-else-if =" report.pass2_complete && pass2Findings.length === 0"
348- class =" alert"
349- :class =" pass1SupplyChainCount > 0 ? 'alert-info' : 'alert-success'"
350- >
349+ <div v-else-if =" report.pass2_complete" class =" alert alert-success" >
351350 <svg class =" w-5 h-5" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" >
352351 <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
353352 </svg >
354- <span v-if =" pass1SupplyChainCount > 0" >Supply chain audit complete. No additional CVEs beyond the {{ pass1SupplyChainCount }} already reported above.</span >
355- <span v-else >Supply chain audit complete. No CVEs found in dependencies.</span >
353+ <span >Supply chain audit complete. No CVEs found in dependencies.</span >
356354 </div >
357355
358356 <!-- Scanner Execution Logs -->
@@ -520,19 +518,24 @@ const riskScoreClass = computed(() => {
520518 return ' badge-success'
521519})
522520
523- // Threat type grouping (same logic as ServerDetail.vue)
524- const threatTypeLabels: Record <ThreatType , string > = {
521+ // Threat type grouping. Real CVE/package findings are routed to the dedicated
522+ // Supply Chain Audit section via the `supply_chain_audit` flag instead of the
523+ // `supply_chain` threat type, so they are filtered out of `groupedFindings`.
524+ // 'uncategorized' is rendered as "Other Findings" so AI-scanner output that
525+ // ClassifyThreat can't pattern-match stays visible instead of silently vanishing.
526+ const threatTypeLabels: Record <Exclude <ThreatType , ' supply_chain' >, string > = {
525527 tool_poisoning: ' Tool Poisoning' ,
526528 prompt_injection: ' Prompt Injection' ,
527529 rug_pull: ' Rug Pull Detection' ,
528- supply_chain: ' Supply Chain (CVEs)' ,
529530 malicious_code: ' Malicious Code' ,
531+ uncategorized: ' Other Findings' ,
530532}
531533
532- const dangerousTypes: ThreatType [] = [' tool_poisoning' , ' prompt_injection' , ' rug_pull' , ' malicious_code' ]
534+ type DisplayThreatType = Exclude <ThreatType , ' supply_chain' >
535+ const dangerousTypes: DisplayThreatType [] = [' tool_poisoning' , ' prompt_injection' , ' rug_pull' , ' malicious_code' ]
533536
534537interface FindingGroup {
535- type: ThreatType
538+ type: DisplayThreatType
536539 label: string
537540 findings: SecurityScanFinding []
538541 defaultOpen: boolean
@@ -542,19 +545,27 @@ interface FindingGroup {
542545const groupedFindings = computed <FindingGroup []>(() => {
543546 if (! report .value ?.findings ) return []
544547
545- const pass1Findings = report .value .findings .filter (
546- (f : SecurityScanFinding ) => ! f .scan_pass || f .scan_pass === 1
548+ // Pull out CVE/package findings; they render in the Supply Chain Audit section.
549+ // Everything else is grouped by threat_type regardless of which pass produced it,
550+ // so AI-scanner findings that only surface during the deep Pass 2 scan land in
551+ // their proper category instead of the CVE list.
552+ const nonCveFindings = report .value .findings .filter (
553+ (f : SecurityScanFinding ) => ! f .supply_chain_audit
547554 )
548555
549- const groups = new Map <ThreatType , SecurityScanFinding []>()
550- for (const f of pass1Findings ) {
551- const type = (f .threat_type || ' supply_chain' ) as ThreatType
556+ const groups = new Map <DisplayThreatType , SecurityScanFinding []>()
557+ for (const f of nonCveFindings ) {
558+ const rawType = (f .threat_type || ' uncategorized' ) as ThreatType
559+ // Legacy data may still carry threat_type === 'supply_chain' on a non-CVE
560+ // finding. Fold it into 'uncategorized' so it stays visible instead of
561+ // being silently dropped.
562+ const type: DisplayThreatType = rawType === ' supply_chain' ? ' uncategorized' : rawType
552563 if (! groups .has (type )) groups .set (type , [])
553564 groups .get (type )! .push (f )
554565 }
555566
556567 const result: FindingGroup [] = []
557- const typeOrder: ThreatType [] = [' tool_poisoning' , ' prompt_injection' , ' rug_pull' , ' malicious_code' , ' supply_chain ' ]
568+ const typeOrder: DisplayThreatType [] = [' tool_poisoning' , ' prompt_injection' , ' rug_pull' , ' malicious_code' , ' uncategorized ' ]
558569 for (const type of typeOrder ) {
559570 const findings = groups .get (type )
560571 if (! findings ) continue
@@ -570,24 +581,17 @@ const groupedFindings = computed<FindingGroup[]>(() => {
570581 return result
571582})
572583
573- const pass1SupplyChainCount = computed (() => {
574- if (! report .value ?.findings ) return 0
575- return report .value .findings .filter (
576- (f : SecurityScanFinding ) => (! f .scan_pass || f .scan_pass === 1 ) && f .threat_type === ' supply_chain'
577- ).length
578- })
579-
580- const pass2Findings = computed <SecurityScanFinding []>(() => {
584+ const supplyChainFindings = computed <SecurityScanFinding []>(() => {
581585 if (! report .value ?.findings ) return []
582- return report .value .findings .filter ((f : SecurityScanFinding ) => f .scan_pass === 2 )
586+ return report .value .findings .filter ((f : SecurityScanFinding ) => f .supply_chain_audit === true )
583587})
584588
585- const pass2HasDangerous = computed (() => {
586- return pass2Findings .value .some (f => f .threat_level === ' dangerous' )
589+ const supplyChainHasDangerous = computed (() => {
590+ return supplyChainFindings .value .some (f => f .threat_level === ' dangerous' )
587591})
588592
589- const pass2HasWarnings = computed (() => {
590- return pass2Findings .value .some (f => f .threat_level === ' warning' )
593+ const supplyChainHasWarnings = computed (() => {
594+ return supplyChainFindings .value .some (f => f .threat_level === ' warning' )
591595})
592596
593597function formatDate(dateStr : string ): string {
0 commit comments