11package reporting
22
33import (
4+ "fmt"
45 "os"
6+ "strings"
7+ "sync/atomic"
58
9+ "github.com/projectdiscovery/gologger"
610 "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
711 json_exporter "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter"
812 "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
3539
3640// Tracker is an interface implemented by an issue tracker
3741type Tracker interface {
42+ // Name returns the name of the tracker
43+ Name () string
3844 // CreateIssue creates an issue in the tracker
39- CreateIssue (event * output.ResultEvent ) error
45+ CreateIssue (event * output.ResultEvent ) (* filters.CreateIssueResponse , error )
46+ // CloseIssue closes an issue in the tracker
47+ CloseIssue (event * output.ResultEvent ) error
4048 // ShouldFilter determines if the event should be filtered out
4149 ShouldFilter (event * output.ResultEvent ) bool
4250}
@@ -55,10 +63,17 @@ type ReportingClient struct {
5563 exporters []Exporter
5664 options * Options
5765 dedupe * dedupe.Storage
66+
67+ stats map [string ]* IssueTrackerStats
68+ }
69+
70+ type IssueTrackerStats struct {
71+ Created atomic.Int32
72+ Failed atomic.Int32
5873}
5974
6075// New creates a new nuclei issue tracker reporting client
61- func New (options * Options , db string ) (Client , error ) {
76+ func New (options * Options , db string , doNotDedupe bool ) (Client , error ) {
6277 client := & ReportingClient {options : options }
6378
6479 if options .GitHub != nil {
@@ -142,6 +157,20 @@ func New(options *Options, db string) (Client, error) {
142157 client .exporters = append (client .exporters , exporter )
143158 }
144159
160+ if doNotDedupe {
161+ return client , nil
162+ }
163+
164+ client .stats = make (map [string ]* IssueTrackerStats )
165+ for _ , tracker := range client .trackers {
166+ trackerName := tracker .Name ()
167+
168+ client .stats [trackerName ] = & IssueTrackerStats {
169+ Created : atomic.Int32 {},
170+ Failed : atomic.Int32 {},
171+ }
172+ }
173+
145174 storage , err := dedupe .New (db )
146175 if err != nil {
147176 return nil , err
@@ -195,7 +224,30 @@ func (c *ReportingClient) RegisterExporter(exporter Exporter) {
195224
196225// Close closes the issue tracker reporting client
197226func (c * ReportingClient ) Close () {
198- c .dedupe .Close ()
227+ // If we have stats for the trackers, print them
228+ if len (c .stats ) > 0 {
229+ for _ , tracker := range c .trackers {
230+ trackerName := tracker .Name ()
231+
232+ if stats , ok := c .stats [trackerName ]; ok {
233+ created := stats .Created .Load ()
234+ if created == 0 {
235+ continue
236+ }
237+ var msgBuilder strings.Builder
238+ msgBuilder .WriteString (fmt .Sprintf ("%d %s tickets created successfully" , created , trackerName ))
239+ failed := stats .Failed .Load ()
240+ if failed > 0 {
241+ msgBuilder .WriteString (fmt .Sprintf (", %d failed" , failed ))
242+ }
243+ gologger .Info ().Msgf (msgBuilder .String ())
244+ }
245+ }
246+ }
247+
248+ if c .dedupe != nil {
249+ c .dedupe .Close ()
250+ }
199251 for _ , exporter := range c .exporters {
200252 exporter .Close ()
201253 }
@@ -211,15 +263,37 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
211263 return nil
212264 }
213265
214- unique , err := c .dedupe .Index (event )
266+ var err error
267+ unique := true
268+ if c .dedupe != nil {
269+ unique , err = c .dedupe .Index (event )
270+ }
215271 if unique {
272+ event .IssueTrackers = make (map [string ]output.IssueTrackerMetadata )
273+
216274 for _ , tracker := range c .trackers {
217275 // process tracker specific allow/deny list
218276 if tracker .ShouldFilter (event ) {
219277 continue
220278 }
221- if trackerErr := tracker .CreateIssue (event ); trackerErr != nil {
279+ trackerName := tracker .Name ()
280+ stats , statsOk := c .stats [trackerName ]
281+
282+ reportData , trackerErr := tracker .CreateIssue (event )
283+ if trackerErr != nil {
284+ if statsOk {
285+ _ = stats .Failed .Add (1 )
286+ }
222287 err = multierr .Append (err , trackerErr )
288+ continue
289+ }
290+ if statsOk {
291+ _ = stats .Created .Add (1 )
292+ }
293+
294+ event .IssueTrackers [tracker .Name ()] = output.IssueTrackerMetadata {
295+ IssueID : reportData .IssueID ,
296+ IssueURL : reportData .IssueURL ,
223297 }
224298 }
225299 for _ , exporter := range c .exporters {
@@ -231,10 +305,25 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
231305 return err
232306}
233307
308+ // CloseIssue closes an issue in the tracker
309+ func (c * ReportingClient ) CloseIssue (event * output.ResultEvent ) error {
310+ for _ , tracker := range c .trackers {
311+ if tracker .ShouldFilter (event ) {
312+ continue
313+ }
314+ if err := tracker .CloseIssue (event ); err != nil {
315+ return err
316+ }
317+ }
318+ return nil
319+ }
320+
234321func (c * ReportingClient ) GetReportingOptions () * Options {
235322 return c .options
236323}
237324
238325func (c * ReportingClient ) Clear () {
239- c .dedupe .Clear ()
326+ if c .dedupe != nil {
327+ c .dedupe .Clear ()
328+ }
240329}
0 commit comments