diff --git a/api/curl.go b/api/curl.go index 919b94f..665c556 100644 --- a/api/curl.go +++ b/api/curl.go @@ -43,7 +43,7 @@ func (h *Handler) ScanURL(ctx *gin.Context) { return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}, form.Opts.Scans, form.Opts.ExcludeScans) if err != nil { analyticsx.TrackError(ctx, serverApiUrlTracer, err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/api/graphql.go b/api/graphql.go index 7db5e3c..ec8432f 100644 --- a/api/graphql.go +++ b/api/graphql.go @@ -39,7 +39,7 @@ func (h *Handler) ScanGraphQL(ctx *gin.Context) { return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}, form.Opts.Scans, form.Opts.ExcludeScans) if err != nil { analyticsx.TrackError(ctx, serverApiGraphQLTracer, err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/api/openapi.go b/api/openapi.go index 97fe604..68df346 100644 --- a/api/openapi.go +++ b/api/openapi.go @@ -66,7 +66,7 @@ func (h *Handler) ScanOpenAPI(ctx *gin.Context) { return } - reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}) + reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) {}, form.Opts.Scans, form.Opts.ExcludeScans) if err != nil { analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -81,5 +81,11 @@ func (h *Handler) ScanOpenAPI(ctx *gin.Context) { Reports: reporter.GetReports(), } _, err = json.Marshal(response) + if err != nil { + analyticsx.TrackError(ctx, serverApiOpenAPITracer, err) + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + ctx.JSON(http.StatusOK, response) } diff --git a/api/request.go b/api/request.go index 0e7e7bd..98b1c15 100644 --- a/api/request.go +++ b/api/request.go @@ -9,6 +9,9 @@ import ( type ScanOptions struct { RateLimit int `json:"rateLimit"` ProxyURL string `json:"proxy"` + + Scans []string `json:"scans"` + ExcludeScans []string `json:"excludeScans"` } func parseScanOptions(opts *ScanOptions) request.NewClientOptions { diff --git a/cmd/discover/api.go b/cmd/discover/api.go index c6facb9..8d298e2 100644 --- a/cmd/discover/api.go +++ b/cmd/discover/api.go @@ -39,7 +39,7 @@ func NewAPICmd() (apiCmd *cobra.Command) { bar := internalCmd.NewProgressBar(len(s.GetOperationsScans())) reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { bar.Add(1) - }) + }, internalCmd.GetIncludeScans(), internalCmd.GetExcludeScans()) if err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) diff --git a/cmd/discover/domain.go b/cmd/discover/domain.go index 6858d1b..15c57f5 100644 --- a/cmd/discover/domain.go +++ b/cmd/discover/domain.go @@ -45,7 +45,7 @@ func NewDomainCmd() (domainCmd *cobra.Command) { bar := internalCmd.NewProgressBar(len(s.GetOperationsScans())) reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { bar.Add(1) - }) + }, internalCmd.GetIncludeScans(), internalCmd.GetExcludeScans()) if err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) diff --git a/cmd/scan/curl.go b/cmd/scan/curl.go index 8edd551..60d9c23 100644 --- a/cmd/scan/curl.go +++ b/cmd/scan/curl.go @@ -49,7 +49,7 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { bar := internalCmd.NewProgressBar(len(s.GetOperationsScans())) if reporter, _, err = s.Execute(func(operationScan *scan.OperationScan) { bar.Add(1) - }); err != nil { + }, internalCmd.GetIncludeScans(), internalCmd.GetExcludeScans()); err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) } diff --git a/cmd/scan/graphql.go b/cmd/scan/graphql.go index 7012b12..0c3889d 100644 --- a/cmd/scan/graphql.go +++ b/cmd/scan/graphql.go @@ -41,7 +41,7 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { bar := internalCmd.NewProgressBar(len(s.GetOperationsScans())) if reporter, _, err = s.Execute(func(operationScan *scan.OperationScan) { bar.Add(1) - }); err != nil { + }, internalCmd.GetIncludeScans(), internalCmd.GetExcludeScans()); err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) } diff --git a/cmd/scan/openapi.go b/cmd/scan/openapi.go index 3276c4f..b0bff88 100644 --- a/cmd/scan/openapi.go +++ b/cmd/scan/openapi.go @@ -83,7 +83,7 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { bar := internalCmd.NewProgressBar(len(s.GetOperationsScans())) if reporter, _, err = s.Execute(func(operationScan *scan.OperationScan) { bar.Add(1) - }); err != nil { + }, internalCmd.GetIncludeScans(), internalCmd.GetExcludeScans()); err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) } diff --git a/internal/cmd/args.go b/internal/cmd/args.go index bc8ebf1..483e879 100644 --- a/internal/cmd/args.go +++ b/internal/cmd/args.go @@ -8,6 +8,9 @@ var ( rateLimit string proxy string + includeScans = []string{"*"} + excludeScans = []string{} + placeholderString string placeholderBool bool ) @@ -17,8 +20,11 @@ var defaultRateLimit = "10/s" func AddCommonArgs(cmd *cobra.Command) { cmd.Flags().StringVarP(&rateLimit, "rate-limit", "r", defaultRateLimit, "Rate limit for requests (e.g. 10/s, 1/m)") cmd.Flags().StringVarP(&proxy, "proxy", "p", "", "Proxy URL for requests") - cmd.Flags().StringArrayVarP(&headers, "header", "H", nil, "Headers to include in requests") - cmd.Flags().StringArrayVarP(&cookies, "cookie", "c", nil, "Cookies to include in requests") + cmd.Flags().StringArrayVarP(&headers, "header", "H", headers, "Headers to include in requests") + cmd.Flags().StringArrayVarP(&cookies, "cookie", "c", cookies, "Cookies to include in requests") + + cmd.Flags().StringArrayVarP(&includeScans, "scans", "", includeScans, "Include specific scans") + cmd.Flags().StringArrayVarP(&excludeScans, "exclude-scans", "e", excludeScans, "Exclude specific scans") } func AddPlaceholderArgs(cmd *cobra.Command) { @@ -46,3 +52,11 @@ func GetRateLimit() string { func GetProxy() string { return proxy } + +func GetIncludeScans() []string { + return includeScans +} + +func GetExcludeScans() []string { + return excludeScans +} diff --git a/scan/operation_scan.go b/scan/operation_scan.go new file mode 100644 index 0000000..5d0b380 --- /dev/null +++ b/scan/operation_scan.go @@ -0,0 +1,26 @@ +package scan + +import ( + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/report" +) + +type OperationScanHandlerFunc func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) + +type OperationScanHandler struct { + ID string + Handler OperationScanHandlerFunc +} + +type OperationScan struct { + Operation *request.Operation + ScanHandler *OperationScanHandler +} + +func NewOperationScanHandler(id string, handler OperationScanHandlerFunc) *OperationScanHandler { + return &OperationScanHandler{ + ID: id, + Handler: handler, + } +} diff --git a/scan/operation_scan_test.go b/scan/operation_scan_test.go new file mode 100644 index 0000000..044ecc5 --- /dev/null +++ b/scan/operation_scan_test.go @@ -0,0 +1,24 @@ +package scan_test + +import ( + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/report" + "github.com/cerberauth/vulnapi/scan" + "github.com/stretchr/testify/assert" +) + +func TestNewOperationScanHandler(t *testing.T) { + handlerFunc := func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + } + handlerID := "test-handler" + + handler := scan.NewOperationScanHandler(handlerID, handlerFunc) + + assert.NotNil(t, handler) + assert.Equal(t, handlerID, handler.ID) + assert.NotNil(t, handler.Handler) +} diff --git a/scan/scan.go b/scan/scan.go index c18406f..1b932a1 100644 --- a/scan/scan.go +++ b/scan/scan.go @@ -2,19 +2,12 @@ package scan import ( "fmt" + "regexp" - "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/report" ) -type OperationScan struct { - Operation *request.Operation - Handler ScanHandler -} - -type ScanHandler func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) - type Scan struct { Operations request.Operations Reporter *report.Reporter @@ -41,34 +34,48 @@ func (s *Scan) GetOperationsScans() []OperationScan { return s.OperationsScans } -func (s *Scan) AddOperationScanHandler(handler ScanHandler) *Scan { +func (s *Scan) AddOperationScanHandler(handler *OperationScanHandler) *Scan { for _, operation := range s.Operations { s.OperationsScans = append(s.OperationsScans, OperationScan{ - Operation: operation, - Handler: handler, + Operation: operation, + ScanHandler: handler, }) } return s } -func (s *Scan) AddScanHandler(handler ScanHandler) *Scan { +func (s *Scan) AddScanHandler(handler *OperationScanHandler) *Scan { s.OperationsScans = append(s.OperationsScans, OperationScan{ - Operation: s.Operations[0], - Handler: handler, + Operation: s.Operations[0], + ScanHandler: handler, }) return s } -func (s *Scan) Execute(scanCallback func(operationScan *OperationScan)) (*report.Reporter, []error, error) { +func (s *Scan) Execute(scanCallback func(operationScan *OperationScan), includeScans []string, excludeScans []string) (*report.Reporter, []error, error) { if scanCallback == nil { scanCallback = func(operationScan *OperationScan) {} } var errors []error for _, scan := range s.OperationsScans { - report, err := scan.Handler(scan.Operation, scan.Operation.SecuritySchemes[0]) // TODO: handle multiple security schemes + if scan.ScanHandler == nil { + continue + } + + // Check if the scan should be excluded + if len(excludeScans) > 0 && contains(excludeScans, scan.ScanHandler.ID) { + continue + } + + // Check if the scan should be included + if len(includeScans) > 0 && !contains(includeScans, scan.ScanHandler.ID) { + continue + } + + report, err := scan.ScanHandler.Handler(scan.Operation, scan.Operation.SecuritySchemes[0]) // TODO: handle multiple security schemes if err != nil { errors = append(errors, err) } @@ -82,3 +89,17 @@ func (s *Scan) Execute(scanCallback func(operationScan *OperationScan)) (*report return s.Reporter, errors, nil } + +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + + match, _ := regexp.MatchString(s, item) + if match { + return true + } + } + return false +} diff --git a/scan/scan_test.go b/scan/scan_test.go index eb08210..48c0b78 100644 --- a/scan/scan_test.go +++ b/scan/scan_test.go @@ -67,11 +67,139 @@ func TestScanGetOperationsScans(t *testing.T) { operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) operations := request.Operations{operation} s, _ := scan.NewScan(operations, nil) - s.AddOperationScanHandler(func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + s.AddOperationScanHandler(scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { return nil, nil - }) + })) operationsScans := s.GetOperationsScans() assert.Equal(t, 1, len(operationsScans)) } + +func TestScanExecuteWithNoHandlers(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + + reporter, errors, err := s.Execute(nil, nil, nil) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 0, len(reporter.Reports)) +} + +func TestScanExecuteWithHandler(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, nil, nil) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 1, len(reporter.Reports)) + assert.Equal(t, "test-report", reporter.Reports[0].ID) +} + +func TestScanExecuteWithIncludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, []string{"test-handler"}, nil) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 1, len(reporter.Reports)) + assert.Equal(t, "test-report", reporter.Reports[0].ID) +} + +func TestScanExecuteWithMatchStringIncludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, []string{"category.*"}, nil) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 1, len(reporter.Reports)) + assert.Equal(t, "test-report", reporter.Reports[0].ID) +} + +func TestScanExecuteWithWrongMatchStringIncludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, []string{"wrong-category.*"}, nil) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 0, len(reporter.Reports)) +} + +func TestScanExecuteWithExcludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, nil, []string{"test-handler"}) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 0, len(reporter.Reports)) +} + +func TestScanExecuteWithMatchStringExcludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, nil, []string{"category.*"}) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 0, len(reporter.Reports)) +} + +func TestScanExecuteWithWrongMatchStringExcludeScans(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) + operations := request.Operations{operation} + s, _ := scan.NewScan(operations, nil) + handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil + }) + s.AddOperationScanHandler(handler) + + reporter, errors, err := s.Execute(nil, nil, []string{"wrong-category.*"}) + + require.NoError(t, err) + assert.Empty(t, errors) + assert.Equal(t, 1, len(reporter.Reports)) + assert.Equal(t, "test-report", reporter.Reports[0].ID) +} diff --git a/scenario/common_scans.go b/scenario/common_scans.go index 99c96a7..2ff598a 100644 --- a/scenario/common_scans.go +++ b/scenario/common_scans.go @@ -17,20 +17,20 @@ import ( ) func WithAllCommonScans(s *scan.Scan) *scan.Scan { - s.AddScanHandler(fingerprint.ScanHandler) - s.AddOperationScanHandler(acceptunauthenticated.ScanHandler) + s.AddScanHandler(scan.NewOperationScanHandler(fingerprint.DiscoverFingerPrintScanID, fingerprint.ScanHandler)) - s.AddOperationScanHandler(authenticationbypass.ScanHandler) - s.AddOperationScanHandler(algnone.ScanHandler) - s.AddOperationScanHandler(blanksecret.ScanHandler) - s.AddOperationScanHandler(notverified.ScanHandler) - s.AddOperationScanHandler(nullsignature.ScanHandler) - s.AddOperationScanHandler(weaksecret.ScanHandler) + s.AddOperationScanHandler(scan.NewOperationScanHandler(acceptunauthenticated.NoAuthOperationScanID, acceptunauthenticated.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(authenticationbypass.AcceptsUnauthenticatedOperationScanID, authenticationbypass.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(algnone.AlgNoneJwtScanID, algnone.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(blanksecret.BlankSecretVulnerabilityScanID, blanksecret.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(notverified.NotVerifiedJwtScanID, notverified.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(nullsignature.NullSignatureScanID, nullsignature.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(weaksecret.WeakSecretVulnerabilityScanID, weaksecret.ScanHandler)) - s.AddOperationScanHandler(httpcookies.ScanHandler) - s.AddOperationScanHandler(httpheaders.ScanHandler) - s.AddOperationScanHandler(httptrace.ScanHandler) - s.AddOperationScanHandler(httptrack.ScanHandler) + s.AddOperationScanHandler(scan.NewOperationScanHandler(httpcookies.HTTPCookiesScanID, httpcookies.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(httpheaders.HTTPHeadersScanID, httpheaders.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(httptrace.HTTPTraceScanID, httptrace.ScanHandler)) + s.AddOperationScanHandler(scan.NewOperationScanHandler(httptrack.HTTPTrackScanID, httptrack.ScanHandler)) return s } diff --git a/scenario/discover_api.go b/scenario/discover_api.go index b80569e..b747109 100644 --- a/scenario/discover_api.go +++ b/scenario/discover_api.go @@ -30,7 +30,9 @@ func NewDiscoverAPIScan(method string, url string, client *request.Client, repor return nil, err } - urlScan.AddScanHandler(fingerprint.ScanHandler).AddScanHandler(discoverableopenapi.ScanHandler).AddScanHandler(discoverablegraphql.ScanHandler) + urlScan.AddScanHandler(scan.NewOperationScanHandler(fingerprint.DiscoverFingerPrintScanID, fingerprint.ScanHandler)) + urlScan.AddScanHandler(scan.NewOperationScanHandler(discoverableopenapi.DiscoverableOpenAPIScanID, discoverableopenapi.ScanHandler)) + urlScan.AddScanHandler(scan.NewOperationScanHandler(discoverablegraphql.DiscoverableGraphQLPathScanID, discoverablegraphql.ScanHandler)) return urlScan, nil } diff --git a/scenario/discover_domain.go b/scenario/discover_domain.go index 7542cc5..a1465c2 100644 --- a/scenario/discover_domain.go +++ b/scenario/discover_domain.go @@ -115,7 +115,9 @@ func NewDiscoverDomainsScan(rootDomain string, client *request.Client, reporter return nil, err } - domainScan.AddScanHandler(fingerprint.ScanHandler).AddScanHandler(discoverableopenapi.ScanHandler).AddScanHandler(discoverablegraphql.ScanHandler) + domainScan.AddScanHandler(scan.NewOperationScanHandler(fingerprint.DiscoverFingerPrintScanID, fingerprint.ScanHandler)) + domainScan.AddScanHandler(scan.NewOperationScanHandler(discoverableopenapi.DiscoverableOpenAPIScanID, discoverableopenapi.ScanHandler)) + domainScan.AddScanHandler(scan.NewOperationScanHandler(discoverablegraphql.DiscoverableGraphQLPathScanID, discoverablegraphql.ScanHandler)) domainsScan = append(domainsScan, domainScan) } } diff --git a/scenario/graphql.go b/scenario/graphql.go index 98b3a31..b5fb67a 100644 --- a/scenario/graphql.go +++ b/scenario/graphql.go @@ -44,7 +44,7 @@ func NewGraphQLScan(url string, client *request.Client, reporter *report.Reporte return nil, err } - graphqlScan.AddScanHandler(introspectionenabled.ScanHandler) + graphqlScan.AddScanHandler(scan.NewOperationScanHandler(introspectionenabled.GraphqlIntrospectionScanID, introspectionenabled.ScanHandler)) WithAllCommonScans(graphqlScan) return graphqlScan, nil diff --git a/scenario/url.go b/scenario/url.go index f6f1fdf..58c0c7c 100644 --- a/scenario/url.go +++ b/scenario/url.go @@ -47,7 +47,8 @@ func NewURLScan(method string, url string, data string, client *request.Client, return nil, err } - urlScan.AddScanHandler(discoverableopenapi.ScanHandler).AddScanHandler(discoverablegraphql.ScanHandler) + urlScan.AddScanHandler(scan.NewOperationScanHandler(discoverableopenapi.DiscoverableOpenAPIScanID, discoverableopenapi.ScanHandler)) + urlScan.AddScanHandler(scan.NewOperationScanHandler(discoverablegraphql.DiscoverableGraphQLPathScanID, discoverablegraphql.ScanHandler)) WithAllCommonScans(urlScan) return urlScan, nil