/** * Security composable for SBOM and security scanning */ import { computed, type ComputedRef, type Ref, ref } from 'vue' import { useComplianceStore } from '../plugin' import type { SBOM, SBOMComponent, SecurityIssue, Vulnerability, SecurityScanResult, } from '@breakpilot/compliance-sdk-types' export interface UseSecurityReturn { // SBOM sbom: ComputedRef components: ComputedRef componentsByCategory: ComputedRef> vulnerableComponents: ComputedRef generateSBOM: (source?: string) => Promise exportSBOM: (format: 'cyclonedx' | 'spdx') => Promise // Security Issues securityIssues: ComputedRef criticalIssues: ComputedRef openIssues: ComputedRef issuesBySeverity: ComputedRef> // Scanning scan: (options?: ScanOptions) => Promise lastScanResult: Ref isScanning: Ref // Vulnerabilities vulnerabilities: ComputedRef getVulnerabilitiesForComponent: (componentId: string) => Vulnerability[] // Loading state isLoading: Ref error: Ref } export interface ScanOptions { tools?: string[] targetPath?: string excludePaths?: string[] } export function useSecurity(): UseSecurityReturn { const store = useComplianceStore() const { state, security } = store const isLoading = ref(false) const isScanning = ref(false) const error = ref(null) const lastScanResult = ref(null) // SBOM const sbom = computed(() => state.sbom) const components = computed(() => state.sbom?.components || []) const componentsByCategory = computed(() => { const comps = state.sbom?.components || [] return comps.reduce( (acc, comp) => { const cat = comp.category || 'other' if (!acc[cat]) acc[cat] = [] acc[cat].push(comp) return acc }, {} as Record ) }) const vulnerableComponents = computed(() => { return components.value.filter(c => c.vulnerabilities && c.vulnerabilities.length > 0) }) const generateSBOM = async (source?: string): Promise => { isLoading.value = true error.value = null try { return await security.generateSBOM(source) } catch (err) { error.value = err instanceof Error ? err : new Error(String(err)) throw err } finally { isLoading.value = false } } const exportSBOM = async (format: 'cyclonedx' | 'spdx'): Promise => { isLoading.value = true error.value = null try { return await security.exportSBOM(format) } catch (err) { error.value = err instanceof Error ? err : new Error(String(err)) throw err } finally { isLoading.value = false } } // Security Issues const securityIssues = computed(() => state.securityIssues) const criticalIssues = computed(() => state.securityIssues.filter(i => i.severity === 'CRITICAL' || i.severity === 'HIGH') ) const openIssues = computed(() => state.securityIssues.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS') ) const issuesBySeverity = computed(() => { return state.securityIssues.reduce( (acc, issue) => { if (!acc[issue.severity]) acc[issue.severity] = [] acc[issue.severity].push(issue) return acc }, {} as Record ) }) // Scanning const scan = async (options?: ScanOptions): Promise => { isScanning.value = true isLoading.value = true error.value = null try { const result = await security.scan(options) lastScanResult.value = result return result } catch (err) { error.value = err instanceof Error ? err : new Error(String(err)) throw err } finally { isScanning.value = false isLoading.value = false } } // Vulnerabilities const vulnerabilities = computed(() => { const vulns: Vulnerability[] = [] components.value.forEach(comp => { if (comp.vulnerabilities) { vulns.push(...comp.vulnerabilities) } }) return vulns }) const getVulnerabilitiesForComponent = (componentId: string): Vulnerability[] => { const comp = components.value.find(c => c.name === componentId) return comp?.vulnerabilities || [] } return { sbom, components, componentsByCategory, vulnerableComponents, generateSBOM, exportSBOM, securityIssues, criticalIssues, openIssues, issuesBySeverity, scan, lastScanResult, isScanning, vulnerabilities, getVulnerabilitiesForComponent, isLoading, error, } }