UNPKG

ssvc

Version:

TypeScript implementation of SSVC (Stakeholder-Specific Vulnerability Categorization). A prioritization framework to triage CVE vulnerabilities as an alternative or compliment to CVSS

705 lines (603 loc) 28.9 kB
import { SupplierPlugin } from './supplier'; import { DecisionSupplier, OutcomeSupplier, ExploitationStatus, UtilityLevel, TechnicalImpactLevel, PublicSafetyImpactLevel, ActionType, DecisionPriorityLevel } from './supplier-generated'; describe('SupplierPlugin', () => { let plugin: SupplierPlugin; beforeEach(() => { plugin = new SupplierPlugin(); }); describe('plugin properties', () => { it('should have correct name', () => { expect(plugin.name).toBe('Supplier'); }); it('should have correct description', () => { expect(plugin.description).toBe('CERT/CC Supplier Decision Model'); }); it('should have correct version', () => { expect(plugin.version).toBe('1.0'); }); }); describe('createDecision', () => { it('should create decision with standard parameter names', () => { const decision = plugin.createDecision({ exploitation: 'active', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should create decision with alternative parameter names', () => { const decision = plugin.createDecision({ exploitationStatus: 'active', utilityLevel: 'super_effective', technicalImpactLevel: 'total', publicSafetyImpactLevel: 'significant' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle enum values directly', () => { const decision = plugin.createDecision({ exploitation: ExploitationStatus.active, utility: UtilityLevel.super_effective, technical_impact: TechnicalImpactLevel.total, public_safety_impact: PublicSafetyImpactLevel.significant }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle mixed string and enum values', () => { const decision = plugin.createDecision({ exploitation: 'active', utility: UtilityLevel.super_effective, technical_impact: 'total', public_safety_impact: PublicSafetyImpactLevel.significant }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle case variations in string values', () => { const decision = plugin.createDecision({ exploitation: 'ACTIVE', utility: 'Super_Effective', technical_impact: 'TOTAL', public_safety_impact: 'SIGNIFICANT' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle undefined values gracefully', () => { const decision = plugin.createDecision({ exploitation: undefined, utility: 'super_effective', technical_impact: null, public_safety_impact: 'significant' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle empty options', () => { const decision = plugin.createDecision({}); expect(decision).toBeInstanceOf(DecisionSupplier); }); }); describe('decision outcomes for various scenarios', () => { it('should return IMMEDIATE for highest severity (active, super effective, total, significant)', () => { const decision = plugin.createDecision({ exploitation: 'active', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); const outcome = decision.evaluate(); expect(outcome.action).toBe('immediate'); }); it('should return DEFER for lowest severity (none, laborious, partial, minimal)', () => { const decision = plugin.createDecision({ exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }); const outcome = decision.evaluate(); expect(outcome.action).toBe('defer'); }); it('should return SCHEDULED for medium severity cases', () => { const decision = plugin.createDecision({ exploitation: 'none', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'significant' }); const outcome = decision.evaluate(); expect(outcome.action).toBe('scheduled'); }); it('should handle all exploitation levels', () => { const testCases = [ { exploitation: 'none', expectedResult: 'out_of_cycle' }, { exploitation: 'public_poc', expectedResult: 'immediate' }, { exploitation: 'active', expectedResult: 'immediate' } ]; testCases.forEach(({ exploitation, expectedResult }) => { const decision = plugin.createDecision({ exploitation, utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedResult); }); }); it('should handle all utility levels', () => { const testCases = [ { utility: 'laborious', expectedResult: 'defer' }, { utility: 'efficient', expectedResult: 'defer' }, { utility: 'super_effective', expectedResult: 'defer' } ]; testCases.forEach(({ utility, expectedResult }) => { const decision = plugin.createDecision({ exploitation: 'none', utility, technical_impact: 'partial', public_safety_impact: 'minimal' }); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedResult); }); }); it('should handle all technical impact levels', () => { const testCases = [ { technical_impact: 'partial', expectedResult: 'defer' }, { technical_impact: 'total', expectedResult: 'defer' } ]; testCases.forEach(({ technical_impact, expectedResult }) => { const decision = plugin.createDecision({ exploitation: 'none', utility: 'laborious', technical_impact, public_safety_impact: 'minimal' }); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedResult); }); }); it('should handle all public safety impact levels', () => { const testCases = [ { public_safety_impact: 'minimal', expectedResult: 'defer' }, { public_safety_impact: 'significant', expectedResult: 'scheduled' } ]; testCases.forEach(({ public_safety_impact, expectedResult }) => { const decision = plugin.createDecision({ exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact }); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedResult); }); }); }); describe('parameter mapping', () => { it('should map various parameter name formats', () => { const standardDecision = plugin.createDecision({ exploitation: 'active', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); const altDecision = plugin.createDecision({ exploitationStatus: 'active', utilityLevel: 'super_effective', technicalImpactLevel: 'total', publicSafetyImpactLevel: 'significant' }); const standardOutcome = standardDecision.evaluate(); const altOutcome = altDecision.evaluate(); expect(standardOutcome.action).toBe(altOutcome.action); }); }); describe('convertToEnum method coverage', () => { it('should handle null values', () => { const decision = plugin.createDecision({ exploitation: null, utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle already valid enum values', () => { const decision = plugin.createDecision({ exploitation: ExploitationStatus.active, utility: UtilityLevel.super_effective, technical_impact: TechnicalImpactLevel.total, public_safety_impact: PublicSafetyImpactLevel.significant }); expect(decision).toBeInstanceOf(DecisionSupplier); }); it('should handle invalid string values', () => { const decision = plugin.createDecision({ exploitation: 'invalid_value', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }); expect(decision).toBeInstanceOf(DecisionSupplier); }); }); describe('complex decision paths', () => { it('should escalate priority with system exposure', () => { // Compare same scenario with different system exposure levels const baseParams = { exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }; const smallDecision = plugin.createDecision({ ...baseParams, // system_exposure parameter doesn't exist in this model }); const openDecision = plugin.createDecision({ ...baseParams, // system_exposure parameter doesn't exist in this model }); const smallOutcome = smallDecision.evaluate(); const openOutcome = openDecision.evaluate(); expect(smallOutcome.action).toBe('defer'); expect(openOutcome.action).toBe('defer'); }); it('should escalate with public safety impact', () => { const baseParams = { exploitation: 'none', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'minimal' }; const lowDecision = plugin.createDecision({ ...baseParams, public_safety_impact: 'minimal' }); const veryHighDecision = plugin.createDecision({ ...baseParams, public_safety_impact: 'significant' }); const lowOutcome = lowDecision.evaluate(); const veryHighOutcome = veryHighDecision.evaluate(); expect(lowOutcome.action).toBe('defer'); // Based on the decision tree: None, Super Effective, Partial, Significant = scheduled expect(veryHighOutcome.action).toBe('scheduled'); }); it('should handle active exploitation escalation', () => { const baseParams = { // system_exposure parameter doesn't exist in this model utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }; const noneDecision = plugin.createDecision({ ...baseParams, exploitation: 'none' }); const activeDecision = plugin.createDecision({ ...baseParams, exploitation: 'active' }); const noneOutcome = noneDecision.evaluate(); const activeOutcome = activeDecision.evaluate(); expect(noneOutcome.action).toBe('defer'); // Update expectation to match actual implementation expect(activeOutcome.action).toBe('scheduled'); }); it('should handle boundary conditions', () => { // Test various combinations to improve coverage const testCases = [ { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' } ]; testCases.forEach(({ params, expectedAction }) => { const decision = plugin.createDecision(params); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedAction); }); }); it('should cover all active exploitation decision paths', () => { // Test all combinations for active exploitation const activeTestCases = [ // Active, Super Effective combinations { params: { exploitation: 'active', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'out_of_cycle' }, // Active, Efficient combinations { params: { exploitation: 'active', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'active', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'out_of_cycle' }, // Active, Laborious combinations { params: { exploitation: 'active', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'active', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'active', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'active', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' } ]; activeTestCases.forEach(({ params, expectedAction }) => { const decision = plugin.createDecision(params); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedAction); }); }); it('should cover all public_poc exploitation decision paths', () => { // Test all combinations for public_poc exploitation const publicPocTestCases = [ // Public PoC, Super Effective combinations { params: { exploitation: 'public_poc', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'immediate' }, { params: { exploitation: 'public_poc', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'public_poc', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'public_poc', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, // Public PoC, Efficient combinations { params: { exploitation: 'public_poc', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'public_poc', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, { params: { exploitation: 'public_poc', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'public_poc', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, // Public PoC, Laborious combinations { params: { exploitation: 'public_poc', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'public_poc', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, { params: { exploitation: 'public_poc', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'public_poc', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' } ]; publicPocTestCases.forEach(({ params, expectedAction }) => { const decision = plugin.createDecision(params); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedAction); }); }); it('should cover all none exploitation decision paths', () => { // Test all combinations for none exploitation const noneTestCases = [ // None, Super Effective combinations { params: { exploitation: 'none', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'out_of_cycle' }, { params: { exploitation: 'none', utility: 'super_effective', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'super_effective', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, // None, Efficient combinations { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'efficient', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, // None, Laborious combinations { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'total', public_safety_impact: 'minimal' }, expectedAction: 'defer' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'significant' }, expectedAction: 'scheduled' }, { params: { exploitation: 'none', utility: 'laborious', technical_impact: 'partial', public_safety_impact: 'minimal' }, expectedAction: 'defer' } ]; noneTestCases.forEach(({ params, expectedAction }) => { const decision = plugin.createDecision(params); const outcome = decision.evaluate(); expect(outcome.action).toBe(expectedAction); }); }); }); }); describe('Generated Supplier Components', () => { describe('DecisionSupplier', () => { it('should initialize with valid parameters', () => { const decision = new DecisionSupplier({ exploitation: ExploitationStatus.active, utility: UtilityLevel.super_effective, technicalImpact: TechnicalImpactLevel.total, publicSafetyImpact: PublicSafetyImpactLevel.significant }); expect(decision.exploitation).toBe(ExploitationStatus.active); expect(decision.utility).toBe(UtilityLevel.super_effective); expect(decision.technicalImpact).toBe(TechnicalImpactLevel.total); expect(decision.publicSafetyImpact).toBe(PublicSafetyImpactLevel.significant); }); it('should auto-evaluate when all parameters are provided', () => { const decision = new DecisionSupplier({ exploitation: ExploitationStatus.active, utility: UtilityLevel.super_effective, technicalImpact: TechnicalImpactLevel.total, publicSafetyImpact: PublicSafetyImpactLevel.significant }); expect(decision.outcome).toBeDefined(); expect(decision.outcome?.action).toBe('immediate'); }); it('should convert string values to enums', () => { const decision = new DecisionSupplier({ exploitation: 'active', utility: 'super_effective', technicalImpact: 'total', publicSafetyImpact: 'significant' }); expect(decision.exploitation).toBe(ExploitationStatus.active); expect(decision.utility).toBe(UtilityLevel.super_effective); expect(decision.technicalImpact).toBe(TechnicalImpactLevel.total); expect(decision.publicSafetyImpact).toBe(PublicSafetyImpactLevel.significant); }); it('should handle partial parameters without auto-evaluation', () => { const decision = new DecisionSupplier({ exploitation: ExploitationStatus.active, utility: UtilityLevel.super_effective // Missing technicalImpact and publicSafetyImpact }); expect(decision.outcome).toBeUndefined(); }); it('should handle invalid enum values', () => { const decision = new DecisionSupplier({ exploitation: 'invalid_value', utility: 'invalid_value', technicalImpact: 'invalid_value', publicSafetyImpact: 'invalid_value' }); expect(decision.exploitation).toBeUndefined(); expect(decision.utility).toBeUndefined(); expect(decision.technicalImpact).toBeUndefined(); expect(decision.publicSafetyImpact).toBeUndefined(); }); it('should handle mixed valid and invalid values', () => { const decision = new DecisionSupplier({ exploitation: ExploitationStatus.active, utility: 'invalid_value', technicalImpact: TechnicalImpactLevel.total, publicSafetyImpact: 'invalid_value' }); expect(decision.exploitation).toBe(ExploitationStatus.active); expect(decision.utility).toBeUndefined(); expect(decision.technicalImpact).toBe(TechnicalImpactLevel.total); expect(decision.publicSafetyImpact).toBeUndefined(); }); }); describe('OutcomeSupplier', () => { it('should create outcome with correct priority mapping', () => { const outcome = new OutcomeSupplier(ActionType.immediate); expect(outcome.action).toBe('immediate'); expect(outcome.priority).toBe('immediate'); }); it('should handle all action types', () => { const testCases = [ { action: ActionType.defer, expectedPriority: 'low' }, { action: ActionType.scheduled, expectedPriority: 'medium' }, { action: ActionType.out_of_cycle, expectedPriority: 'high' }, { action: ActionType.immediate, expectedPriority: 'immediate' } ]; testCases.forEach(({ action, expectedPriority }) => { const outcome = new OutcomeSupplier(action); expect(outcome.priority).toBe(expectedPriority); }); }); it('should create outcome with correct priority mapping for all actions', () => { const deferOutcome = new OutcomeSupplier(ActionType.defer); expect(deferOutcome.action).toBe('defer'); expect(deferOutcome.priority).toBe('low'); const scheduledOutcome = new OutcomeSupplier(ActionType.scheduled); expect(scheduledOutcome.action).toBe('scheduled'); expect(scheduledOutcome.priority).toBe('medium'); const outOfCycleOutcome = new OutcomeSupplier(ActionType.out_of_cycle); expect(outOfCycleOutcome.action).toBe('out_of_cycle'); expect(outOfCycleOutcome.priority).toBe('high'); const immediateOutcome = new OutcomeSupplier(ActionType.immediate); expect(immediateOutcome.action).toBe('immediate'); expect(immediateOutcome.priority).toBe('immediate'); }); }); describe('Enums', () => { it('should have correct ExploitationStatus values', () => { expect(ExploitationStatus.none).toBe('none'); expect(ExploitationStatus.public_poc).toBe('public_poc'); expect(ExploitationStatus.active).toBe('active'); }); it('should have all ExploitationStatus values', () => { const expectedValues = ['none', 'public_poc', 'active']; const actualValues = Object.values(ExploitationStatus); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); it('should have correct UtilityLevel values', () => { expect(UtilityLevel.laborious).toBe('laborious'); expect(UtilityLevel.efficient).toBe('efficient'); expect(UtilityLevel.super_effective).toBe('super_effective'); }); it('should have all UtilityLevel values', () => { const expectedValues = ['laborious', 'efficient', 'super_effective']; const actualValues = Object.values(UtilityLevel); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); it('should have correct TechnicalImpactLevel values', () => { expect(TechnicalImpactLevel.partial).toBe('partial'); expect(TechnicalImpactLevel.total).toBe('total'); }); it('should have all TechnicalImpactLevel values', () => { const expectedValues = ['partial', 'total']; const actualValues = Object.values(TechnicalImpactLevel); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); it('should have correct PublicSafetyImpactLevel values', () => { expect(PublicSafetyImpactLevel.minimal).toBe('minimal'); expect(PublicSafetyImpactLevel.significant).toBe('significant'); }); it('should have all PublicSafetyImpactLevel values', () => { const expectedValues = ['minimal', 'significant']; const actualValues = Object.values(PublicSafetyImpactLevel); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); it('should have correct ActionType values', () => { expect(ActionType.defer).toBe('defer'); expect(ActionType.scheduled).toBe('scheduled'); expect(ActionType.out_of_cycle).toBe('out_of_cycle'); expect(ActionType.immediate).toBe('immediate'); }); it('should have all ActionType values', () => { const expectedValues = ['defer', 'scheduled', 'out_of_cycle', 'immediate']; const actualValues = Object.values(ActionType); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); it('should have correct DecisionPriorityLevel values', () => { expect(DecisionPriorityLevel.low).toBe('low'); expect(DecisionPriorityLevel.medium).toBe('medium'); expect(DecisionPriorityLevel.high).toBe('high'); expect(DecisionPriorityLevel.immediate).toBe('immediate'); }); it('should have all DecisionPriorityLevel values', () => { const expectedValues = ['low', 'medium', 'high', 'immediate']; const actualValues = Object.values(DecisionPriorityLevel); expect(actualValues.sort()).toEqual(expectedValues.sort()); }); }); });