@@ -91,7 +91,7 @@ async function importFlawsToADO(params) {
9191 // Track which work items are still active (not closed)
9292 const activeWorkItems = existingWorkItems . filter ( wi => {
9393 const state = wi . fields [ 'System.State' ] || 'Unknown' ;
94- return state !== 'Closed ' && state !== 'Resolved' && state !== 'Removed' ;
94+ return state !== 'Done ' && state !== 'Resolved' && state !== 'Removed' ;
9595 } ) ;
9696 console . log ( `Found ${ activeWorkItems . length } active work items to check for closure` ) ;
9797
@@ -123,6 +123,7 @@ async function importFlawsToADO(params) {
123123 let createdCount = 0 ;
124124 let reopenedCount = 0 ;
125125 let skippedCount = 0 ;
126+ let closedCount = 0 ;
126127
127128 if ( scanType === 'pipeline' ) {
128129 const result = await processPipelineFlawsADO ( adoPatchClient , adoOrg , adoProject , adoWorkItemType , flawData , {
@@ -140,6 +141,7 @@ async function importFlawsToADO(params) {
140141 createdCount = result . createdCount ;
141142 reopenedCount = result . reopenedCount ;
142143 skippedCount = result . skippedCount ;
144+ closedCount = closePipelineFlaws ( adoClient , adoOrg , adoProject , activeWorkItems , result . processedFlawIds , commit_hash , debug )
143145 } else {
144146 const result = await processPolicyFlawsADO ( adoPatchClient , adoOrg , adoProject , adoWorkItemType , flawData , {
145147 source_base_path_1,
@@ -359,14 +361,14 @@ function normalizeTitle(title) {
359361function createVeracodeFlawId ( flaw , scanType ) {
360362 if ( scanType === 'pipeline' ) {
361363 // For pipeline scans, use CWE:file:line format
362- const cweId = flaw . cwe_id || 'Unknown' ;
363- const fileName = flaw . files ?. source_file ?. file || 'Unknown' ;
364- const lineNumber = flaw . files ?. source_file ?. line || 'Unknown' ;
365- return `[VID:${ cweId } :${ fileName } :${ lineNumber } ]` ;
364+ const cweName = flaw . issue_type || 'Unknown' ;
365+ const flawId = flaw . issue_id || 'Unknown' ;
366+ return `Veracode Flaw (Static): ${ cweName } , Flaw ${ flawId } `
366367 } else {
367368 // For policy scans, use flaw number format
368- const flawNumber = flaw . issue_id || 'Unknown' ;
369- return `[VID:${ flawNumber } ]` ;
369+ const cweName = flaw . finding_details ?. cwe ?. name || 'Unknown' ;
370+ const flawId = flaw . issue_id || 'Unknown' ;
371+ return `Veracode Flaw (Static): ${ cweName } , Flaw ${ flawId } `
370372 }
371373}
372374
@@ -431,6 +433,99 @@ function validateNoDuplicates(existingWorkItems, veracodeFlawId, debug) {
431433 return matches [ 0 ] || null ;
432434}
433435
436+ function formatMitigation ( annotation ) {
437+ const mitigationStatus = [ 'COMMENT' , 'FP' , 'APPROVED' , 'REJECTED' ]
438+ let mitigation = ''
439+
440+ const created = annotation . created || 'Unknown' ;
441+ const comment = annotation . comment || 'Unknown' ;
442+ const action = annotation . action || 'Unknown' ;
443+ const user_name = annotation . user_name || 'Unknown' ;
444+
445+ const technique = annotation . technique || 'Unknown' ;
446+ const specifics = annotation . specifics || 'Unknown' ;
447+ const remaining_risk = annotation . remaining_risk || 'Unknown' ;
448+ const verification = annotation . verification || 'Unknown' ;
449+
450+ const mitigation_title = created + ":" + user_name + ":" + action ;
451+ mitigation += mitigation_title + "<br>" ;
452+ mitigation += "<b>User:</b> " + user_name + "<br>" ; ;
453+ mitigation += "<b>Created:</b> " + created + "<br>" ; ;
454+ mitigation += "<b>Action:</b> " + action + "<br>" ; ;
455+
456+ if ( ! mitigationStatus . includes ( action ) ) {
457+ mitigation += "<b>Technique:</b> " + technique + "<br/>" ;
458+ mitigation += "<b>Specifics:</b> " + specifics + "<br>" ;
459+ mitigation += "<b>Remaining Risk:</b> " + remaining_risk + "<br>" ;
460+ mitigation += "<b>Verification:</b> " + verification + "<br>" ;
461+ } else {
462+ mitigation += "<b>Comment:</b>" + comment ;
463+ }
464+
465+ return { mitigation_title, mitigation }
466+ }
467+
468+ async function checkExistingComments ( adoClient , url , workItemId ) {
469+ try {
470+ const response = await adoClient . get ( url , {
471+ headers : {
472+ 'Content-Type' : 'application/json'
473+ }
474+ } ) ;
475+
476+ return response . data . comments
477+ } catch ( error ) {
478+ console . error ( `Failed to get comment for work item ${ workItemId } :` , error . message ) ;
479+ throw error ;
480+ }
481+ }
482+
483+ async function updateWorkItem ( adoClient , adoOrg , adoProject , workItemId , annotations , params ) {
484+ const { commit_hash, debug } = params ;
485+ const url = `/${ adoOrg } /${ adoProject } /_apis/wit/workItems/${ workItemId } /comments?api-version=7.0-preview.3` ;
486+
487+ const sorted_annotations = annotations . sort ( function ( a , b ) {
488+ const dateA = new Date ( a . created ) ;
489+ const dateB = new Date ( b . created ) ;
490+ return dateA - dateB ;
491+ } )
492+
493+ for ( const annot of sorted_annotations ) {
494+ const { mitigation_title, mitigation } = formatMitigation ( annot )
495+ const comments = await checkExistingComments ( adoClient , url , workItemId )
496+
497+ let duplicate_comment = comments . find ( ( { text} ) => text . startsWith ( mitigation_title ) )
498+
499+ if ( duplicate_comment === undefined ) {
500+ const payload = { text : mitigation }
501+
502+ addComment ( adoClient , url , workItemId , payload , debug )
503+ } else {
504+ if ( debug === 'true' ) {
505+ console . log ( `Skipping duplicate comment found for work item ${ workItemId } with ${ mitigation_title } ` ) ;
506+ }
507+ }
508+ }
509+ }
510+
511+ async function addComment ( adoClient , url , workItemId , payload , debug ) {
512+ try {
513+ const response = await adoClient . post ( url , payload , {
514+ headers : {
515+ 'Content-Type' : 'application/json'
516+ }
517+ } ) ;
518+ console . log ( 'Work item comment added successfully' ) ;
519+ if ( debug === 'true' ) {
520+ console . log ( 'Adding Mitigation Comments to work item with payload:' , JSON . stringify ( payload , null , 2 ) ) ;
521+ }
522+ return response ;
523+ } catch ( error ) {
524+ console . error ( `Failed to add comment to work item ${ workItemId } :` , error . message ) ;
525+ throw error ;
526+ }
527+ }
528+
434529async function reopenWorkItem ( adoClient , adoOrg , adoProject , workItemId , params ) {
435530 const { source_base_path_1, source_base_path_2, source_base_path_3, commit_hash, debug } = params ;
436531
@@ -439,7 +534,7 @@ async function reopenWorkItem(adoClient, adoOrg, adoProject, workItemId, params)
439534 {
440535 op : 'replace' ,
441536 path : '/fields/System.State' ,
442- value : 'Active'
537+ value : systemState
443538 } ,
444539 {
445540 op : 'add' ,
@@ -453,7 +548,11 @@ async function reopenWorkItem(adoClient, adoOrg, adoProject, workItemId, params)
453548 }
454549
455550 try {
456- const response = await adoClient . patch ( url , payload ) ;
551+ const response = await adoClient . patch ( url , payload , {
552+ headers : {
553+ 'Content-Type' : 'application/json-patch+json'
554+ }
555+ } ) ;
457556 if ( debug === 'true' ) {
458557 console . log ( 'Work item reopened successfully:' , response . data . id ) ;
459558 }
@@ -537,7 +636,11 @@ async function createWorkItem(adoClient, adoOrg, project, workItemType, flaw, pa
537636 }
538637
539638 try {
540- const response = await adoClient . post ( url , payload ) ;
639+ const response = await adoClient . post ( url , payload , {
640+ headers : {
641+ 'Content-Type' : 'application/json-patch+json'
642+ }
643+ } ) ;
541644 if ( debug === 'true' ) {
542645 console . log ( 'Response:' , JSON . stringify ( response . data , null , 2 ) ) ;
543646 }
@@ -669,7 +772,11 @@ async function closeWorkItem(adoClient, adoOrg, adoProject, workItemId, commit_h
669772 }
670773
671774 try {
672- const response = await adoClient . patch ( url , payload ) ;
775+ const response = await adoClient . patch ( url , payload , {
776+ headers : {
777+ 'Content-Type' : 'application/json-patch+json'
778+ }
779+ } ) ;
673780 if ( debug === 'true' ) {
674781 console . log ( 'Work item closed successfully:' , response . data . id ) ;
675782 }
@@ -680,6 +787,43 @@ async function closeWorkItem(adoClient, adoOrg, adoProject, workItemId, commit_h
680787 }
681788}
682789
790+ async function closePipelineFlaws ( adoClient , adoOrg , adoProject , activeWorkItems , processedFlawIds , commit_hash , debug ) {
791+ // Close work items that are no longer present in the scan results
792+ console . log ( `\nChecking for work items to close (flaws not found in current scan)...` ) ;
793+
794+ for ( const workItem of activeWorkItems ) {
795+ try {
796+ const title = workItem . fields [ 'System.Title' ] || '' ;
797+ const workItemId = workItem . id ;
798+
799+ // Check if this work item corresponds to a flaw that's still present
800+ const isStillPresent = Array . from ( processedFlawIds ) . some ( flawId => {
801+ return title . includes ( flawId ) || isWorkItemMatchingFlaw ( workItem , flawId ) ;
802+ } ) ;
803+
804+ if ( ! isStillPresent ) {
805+ console . log ( `Closing work item ${ workItemId } - flaw no longer found in scan: "${ title } "` ) ;
806+ await closeWorkItem ( adoClient , adoOrg , adoProject , workItemId , flawResolution = "CLOSED BY SCAN" , commit_hash , debug ) ;
807+ closedCount ++ ;
808+
809+ // Wait between API calls to avoid rate limiting
810+ await new Promise ( resolve => setTimeout ( resolve , waitTime * 1000 ) ) ;
811+ } else {
812+ if ( debug === 'true' ) {
813+ console . log ( `Keeping work item ${ workItemId } open - flaw still present: "${ title } "` ) ;
814+ }
815+ }
816+ } catch ( error ) {
817+ console . error ( `Failed to close work item ${ workItem . id } : ${ error . message } ` ) ;
818+ if ( fail_build === 'true' ) {
819+ throw error ;
820+ }
821+ }
822+ }
823+
824+ return closedCount
825+ }
826+
683827function isWorkItemMatchingFlaw ( workItem , flawId ) {
684828 const title = workItem . fields [ 'System.Title' ] || '' ;
685829 const tags = workItem . fields [ 'System.Tags' ] || '' ;
@@ -977,6 +1121,9 @@ async function processPolicyFlawsADO(adoPatchClient, adoOrg, adoProject, adoWork
9771121 const flawId = flaw . issue_id || 'Unknown' ;
9781122 const cweId = flaw . finding_details ?. cwe ?. id || 'Unknown' ;
9791123 const cweName = flaw . finding_details ?. cwe ?. name || 'Unknown' ;
1124+ const annotations = flaw . annotations || [ ] ;
1125+ const flawStatus = flaw . finding_status ?. status
1126+ const flawResolution = flaw . finding_status ?. resolution
9801127
9811128 // Create a unique identifier for the flaw
9821129 const veracodeFlawId = createVeracodeFlawId ( flaw , 'policy' ) ;
@@ -1009,6 +1156,7 @@ async function processPolicyFlawsADO(adoPatchClient, adoOrg, adoProject, adoWork
10091156 } else {
10101157 console . log ( `Work item ${ workItemId } is already open (State: ${ workItemState } ), skipping creation` ) ;
10111158 skippedCount ++ ;
1159+
10121160 }
10131161 } else {
10141162 // Create new work item
@@ -1044,7 +1192,7 @@ async function processPolicyFlawsADO(adoPatchClient, adoOrg, adoProject, adoWork
10441192 }
10451193 }
10461194
1047- return { createdCount, reopenedCount, skippedCount } ;
1195+ return { createdCount, reopenedCount, skippedCount, closedCount } ;
10481196}
10491197
10501198module . exports = {
0 commit comments