From 9a12263b78c9d7281e895a9e655c3fc840341a4b Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Tue, 1 Oct 2024 22:14:04 +0200 Subject: [PATCH] feat: refactor and add more properties in report --- api/curl.go | 6 +- api/graphql.go | 6 +- api/openapi.go | 8 +- api/response.go | 2 +- api/response_test.go | 2 +- cmd/scan/curl.go | 2 +- cmd/scan/graphql.go | 2 +- cmd/scan/openapi.go | 2 +- go.mod | 2 +- go.sum | 40 +-- internal/cmd/analytics.go | 6 +- internal/cmd/args.go | 24 +- internal/cmd/args_test.go | 12 +- internal/cmd/printtable/fingerprint_table.go | 2 +- internal/cmd/printtable/report_table.go | 66 ++-- internal/cmd/printtable/report_table_test.go | 96 +++--- .../cmd/printtable/wellknown_paths_table.go | 4 +- internal/cmd/{output.go => report.go} | 22 +- internal/request/operation.go | 66 ++-- internal/request/operation_test.go | 58 +++- internal/request/operations.go | 22 ++ internal/request/operations_test.go | 108 +++++++ internal/scan/scan_url.go | 6 +- openapi/base_url.go | 2 +- openapi/openapi.go | 4 +- openapi/operation.go | 12 +- openapi/security_scheme.go | 4 +- report/curl_report.go | 44 +++ report/curl_report_test.go | 88 ++++++ report/graphql_report.go | 53 ++++ report/issue.go | 2 +- report/issue_report.go | 138 +++++++++ report/{vuln_test.go => issue_report_test.go} | 94 +++--- report/openapi_report.go | 90 ++++++ report/openapi_report_test.go | 87 ++++++ report/options_report.go | 7 + report/report.go | 139 ++++----- report/report_test.go | 105 +++++-- report/reporter.go | 121 ++++++-- report/reporter_test.go | 288 +++++++++++++++--- report/vuln.go | 138 --------- .../authentication_bypass.go | 8 +- .../authentication_bypass_test.go | 6 +- .../jwt/alg_none/alg_none.go | 8 +- .../jwt/alg_none/alg_none_test.go | 8 +- .../jwt/blank_secret/blank_secret.go | 8 +- .../jwt/blank_secret/blank_secret_test.go | 10 +- .../jwt/not_verified/not_verified.go | 10 +- .../jwt/not_verified/not_verified_test.go | 10 +- .../jwt/null_signature/null_signature.go | 8 +- .../jwt/null_signature/null_signature_test.go | 8 +- .../jwt/weak_secret/weak_secret.go | 8 +- .../jwt/weak_secret/weak_secret_test.go | 10 +- .../accept_unauthenticated_operation.go | 6 +- .../accept_unauthenticated_operation_test.go | 4 +- .../discoverable_graphql.go | 4 +- .../discoverable_graphql_test.go | 4 +- .../discoverable_openapi.go | 4 +- .../discoverable_openapi_test.go | 4 +- scan/discover/fingerprint/fingerprint.go | 40 +-- scan/discover/fingerprint/fingerprint_test.go | 18 +- scan/discover/utils.go | 10 +- scan/discover/utils_test.go | 10 +- .../introspection_enabled.go | 10 +- .../introspection_enabled_test.go | 4 +- .../http_cookies/http_cookies.go | 32 +- .../http_cookies/http_cookies_test.go | 30 +- .../http_headers/http_headers.go | 32 +- .../http_headers/http_headers_test.go | 32 +- .../http_trace/http_trace_method.go | 6 +- .../http_trace/http_trace_method_test.go | 4 +- .../http_track/http_track_method.go | 6 +- .../http_track/http_track_method_test.go | 4 +- scan/operation_scan.go | 2 +- scan/operation_scan_test.go | 4 +- scan/scan_test.go | 62 ++-- scenario/graphql.go | 9 + scenario/openapi.go | 9 + scenario/url.go | 10 + 79 files changed, 1631 insertions(+), 811 deletions(-) rename internal/cmd/{output.go => report.go} (80%) create mode 100644 internal/request/operations.go create mode 100644 internal/request/operations_test.go create mode 100644 report/curl_report.go create mode 100644 report/curl_report_test.go create mode 100644 report/graphql_report.go create mode 100644 report/issue_report.go rename report/{vuln_test.go => issue_report_test.go} (65%) create mode 100644 report/openapi_report.go create mode 100644 report/openapi_report_test.go create mode 100644 report/options_report.go delete mode 100644 report/vuln.go diff --git a/api/curl.go b/api/curl.go index b71c35a9..39267b62 100644 --- a/api/curl.go +++ b/api/curl.go @@ -53,11 +53,11 @@ func (h *Handler) ScanURL(ctx *gin.Context) { return } - if reporter.HasVulnerability() { - analyticsx.TrackEvent(ctx, serverApiUrlTracer, "Vulnerability Found", nil) + if reporter.HasIssue() { + analyticsx.TrackEvent(ctx, serverApiUrlTracer, "Issue Found", nil) } ctx.JSON(http.StatusOK, HTTPResponseReports{ - Reports: reporter.GetReports(), + Reports: reporter.GetScanReports(), }) } diff --git a/api/graphql.go b/api/graphql.go index 3896e1f1..b5ce6542 100644 --- a/api/graphql.go +++ b/api/graphql.go @@ -49,11 +49,11 @@ func (h *Handler) ScanGraphQL(ctx *gin.Context) { return } - if reporter.HasVulnerability() { - analyticsx.TrackEvent(ctx, serverApiGraphQLTracer, "Vulnerability Found", nil) + if reporter.HasIssue() { + analyticsx.TrackEvent(ctx, serverApiGraphQLTracer, "Issue Found", nil) } ctx.JSON(http.StatusOK, HTTPResponseReports{ - Reports: reporter.GetReports(), + Reports: reporter.GetScanReports(), }) } diff --git a/api/openapi.go b/api/openapi.go index 32dee39d..027c5c91 100644 --- a/api/openapi.go +++ b/api/openapi.go @@ -19,7 +19,7 @@ type NewOpenAPIScanRequest struct { Schema string `json:"schema" binding:"required"` SecuritySchemes map[string]struct { Value string `json:"value" binding:"required"` - } `json:"security_schemes"` + } `json:"securitySchemes"` Opts *ScanOptions `json:"options"` } @@ -76,12 +76,12 @@ func (h *Handler) ScanOpenAPI(ctx *gin.Context) { return } - if reporter.HasVulnerability() { - analyticsx.TrackEvent(ctx, serverApiOpenAPITracer, "Vulnerability Found", nil) + if reporter.HasIssue() { + analyticsx.TrackEvent(ctx, serverApiOpenAPITracer, "Issue Found", nil) } response := HTTPResponseReports{ - Reports: reporter.GetReports(), + Reports: reporter.GetScanReports(), } _, err = json.Marshal(response) if err != nil { diff --git a/api/response.go b/api/response.go index 2922d5ca..eeed259e 100644 --- a/api/response.go +++ b/api/response.go @@ -5,5 +5,5 @@ import ( ) type HTTPResponseReports struct { - Reports []*report.Report `json:"reports"` + Reports []*report.ScanReport `json:"reports"` } diff --git a/api/response_test.go b/api/response_test.go index 7049c602..ecd5c4db 100644 --- a/api/response_test.go +++ b/api/response_test.go @@ -19,7 +19,7 @@ func TestMarshalHTTPResponseReports(t *testing.T) { sr.EndTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) hrr := api.HTTPResponseReports{ - Reports: []*report.Report{sr}, + Reports: []*report.ScanReport{sr}, } b, err := json.Marshal(hrr) diff --git a/cmd/scan/curl.go b/cmd/scan/curl.go index ce67d3fe..6100a8f4 100644 --- a/cmd/scan/curl.go +++ b/cmd/scan/curl.go @@ -68,7 +68,7 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { } internalCmd.TrackScanReport(ctx, tracer, reporter) - err = internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), reporter) + err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter) if err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) diff --git a/cmd/scan/graphql.go b/cmd/scan/graphql.go index 0bff8742..3edf8f80 100644 --- a/cmd/scan/graphql.go +++ b/cmd/scan/graphql.go @@ -60,7 +60,7 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { } internalCmd.TrackScanReport(ctx, tracer, reporter) - err = internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), reporter) + err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter) if err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) diff --git a/cmd/scan/openapi.go b/cmd/scan/openapi.go index 72edeeb6..602a29c9 100644 --- a/cmd/scan/openapi.go +++ b/cmd/scan/openapi.go @@ -102,7 +102,7 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { } internalCmd.TrackScanReport(ctx, tracer, reporter) - if err = internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), reporter); err != nil { + if err = internalCmd.PrintOrExportReport(internalCmd.GetReportFormat(), internalCmd.GetReportTransport(), reporter); err != nil { analyticsx.TrackError(ctx, tracer, err) log.Fatal(err) } diff --git a/go.mod b/go.mod index ad8d88bc..8ef7a318 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/ratelimit v0.3.1 + golang.org/x/text v0.19.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -73,7 +74,6 @@ require ( golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect diff --git a/go.sum b/go.sum index 1067b686..e6159075 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= -github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= -github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/gin-contrib/requestid v1.0.3 h1:NB6SF0Te4Ikn8mW2K4tegpm2WGuB3bWj4wnWaM4oSAA= @@ -109,18 +107,14 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectdiscovery/wappalyzergo v0.1.21 h1:IBzws+/YyXctWMZOjADRIG3ghv076rT54jbO548/+q4= -github.com/projectdiscovery/wappalyzergo v0.1.21/go.mod h1:wnvmbC10pQTOoCKnCTmWKP20rpEtqrMJZvzuTuleeyw= github.com/projectdiscovery/wappalyzergo v0.1.23 h1:YqZPKTD/NC9xb/lByfMDPWwBNl/5/CevjP5elRAzi5k= github.com/projectdiscovery/wappalyzergo v0.1.23/go.mod h1:wnvmbC10pQTOoCKnCTmWKP20rpEtqrMJZvzuTuleeyw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/progressbar/v3 v3.16.0 h1:+MbBim/cE9DqDb8UXRfLJ6RZdyDkXG1BDy/sWc5s0Mc= -github.com/schollz/progressbar/v3 v3.16.0/go.mod h1:lLiKjKJ9/yzc9Q8jk+sVLfxWxgXKsktvUf6TO+4Y2nw= github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ= github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -147,28 +141,16 @@ go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0. go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.55.0/go.mod h1:8aCCTMjP225r98yevEMM5NYDb3ianWLoeIzZ1rPyxHU= go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -179,42 +161,24 @@ go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cmd/analytics.go b/internal/cmd/analytics.go index c06a8cf8..d2497222 100644 --- a/internal/cmd/analytics.go +++ b/internal/cmd/analytics.go @@ -11,8 +11,8 @@ import ( func TrackScanReport(ctx context.Context, tracer trace.Tracer, reporter *report.Reporter) { analyticsx.TrackEvent(ctx, tracer, "Scan Report", []attribute.KeyValue{ - attribute.Int("vulnerabilityCount", len(reporter.GetVulnerabilityReports())), - attribute.Bool("hasVulnerability", reporter.HasVulnerability()), - attribute.Bool("hasHighRiskSeverityVulnerability", reporter.HasHighRiskOrHigherSeverityVulnerability()), + attribute.Int("issuesCount", len(reporter.GetIssueReports())), + attribute.Bool("hasIssue", reporter.HasIssue()), + attribute.Bool("hasHighRiskSeverityIssue", reporter.HasHighRiskOrHigherSeverityIssue()), }) } diff --git a/internal/cmd/args.go b/internal/cmd/args.go index 0a3f18c5..53a97968 100644 --- a/internal/cmd/args.go +++ b/internal/cmd/args.go @@ -11,10 +11,10 @@ var ( includeScans []string excludeScans []string - outputFormat string - outputTransport string - outputPath string - outputURL string + reportFormat string + reportTransport string + reportFile string + reportURL string noProgress bool severityThreshold float64 @@ -34,10 +34,10 @@ func AddCommonArgs(cmd *cobra.Command) { cmd.Flags().StringArrayVarP(&includeScans, "scans", "", includeScans, "Include specific scans") cmd.Flags().StringArrayVarP(&excludeScans, "exclude-scans", "e", excludeScans, "Exclude specific scans") - cmd.Flags().StringVarP(&outputFormat, "format", "", "table", "Output format (table, json, yaml)") - cmd.Flags().StringVarP(&outputTransport, "output-transport", "", "file", "The transport to use for output (e.g. file, http)") - cmd.Flags().StringVarP(&outputPath, "output-path", "", "", "The file to write the output to") - cmd.Flags().StringVarP(&outputURL, "output-url", "", "", "The URL to send the output to") + cmd.Flags().StringVarP(&reportFormat, "report-format", "", "table", "Report format (table, json, yaml)") + cmd.Flags().StringVarP(&reportTransport, "report-transport", "", "file", "The transport to use for report (e.g. file, http)") + cmd.Flags().StringVarP(&reportFile, "report-file", "", "", "The file to write the report to") + cmd.Flags().StringVarP(&reportURL, "report-url", "", "", "The URL to send the report to") cmd.Flags().BoolVarP(&noProgress, "no-progress", "", false, "Disable progress output") cmd.Flags().Float64VarP(&severityThreshold, "severity-threshold", "", 1, "Threshold to trigger stderr output if at least one vulnerability CVSS is higher") @@ -89,12 +89,12 @@ func GetExcludeScans() []string { return filteredScans } -func GetOutputFormat() string { - return outputFormat +func GetReportFormat() string { + return reportFormat } -func GetOutputTransport() string { - return outputTransport +func GetReportTransport() string { + return reportTransport } func GetNoProgress() bool { diff --git a/internal/cmd/args_test.go b/internal/cmd/args_test.go index 7b97f811..61ee2463 100644 --- a/internal/cmd/args_test.go +++ b/internal/cmd/args_test.go @@ -67,10 +67,10 @@ func TestAddCommonArgs(t *testing.T) { "--cookie=sessionid=12345", "--scans=scan1", "--scans=scan2", - "--format=json", - "--output-transport=http", - "--output-path=/tmp/output", - "--output-url=http://example.com/output", + "--report-format=json", + "--report-transport=http", + "--report-file=/tmp/output", + "--report-url=http://example.com/output", "--no-progress", "--severity-threshold=5", }, @@ -117,8 +117,8 @@ func TestAddCommonArgs(t *testing.T) { assert.Equal(t, tt.expected.cookies, cmd.GetCookies()) assert.Equal(t, tt.expected.includeScans, cmd.GetIncludeScans()) assert.Equal(t, tt.expected.excludeScans, cmd.GetExcludeScans()) - assert.Equal(t, tt.expected.outputFormat, cmd.GetOutputFormat()) - assert.Equal(t, tt.expected.outputTransport, cmd.GetOutputTransport()) + assert.Equal(t, tt.expected.outputFormat, cmd.GetReportFormat()) + assert.Equal(t, tt.expected.outputTransport, cmd.GetReportTransport()) assert.Equal(t, tt.expected.noProgress, cmd.GetNoProgress()) assert.Equal(t, tt.expected.severityThreshold, cmd.GetSeverityThreshold()) }) diff --git a/internal/cmd/printtable/fingerprint_table.go b/internal/cmd/printtable/fingerprint_table.go index bc601d48..941147d2 100644 --- a/internal/cmd/printtable/fingerprint_table.go +++ b/internal/cmd/printtable/fingerprint_table.go @@ -9,7 +9,7 @@ import ( ) func FingerprintScanReport(reporter *report.Reporter) { - report := reporter.GetReportByID(fingerprint.DiscoverFingerPrintScanID) + report := reporter.GetScanReportByID(fingerprint.DiscoverFingerPrintScanID) if report == nil || !report.HasData() { return } diff --git a/internal/cmd/printtable/report_table.go b/internal/cmd/printtable/report_table.go index 027bf2b1..63a97858 100644 --- a/internal/cmd/printtable/report_table.go +++ b/internal/cmd/printtable/report_table.go @@ -2,28 +2,27 @@ package printtable import ( "fmt" - "net/url" "sort" "github.com/cerberauth/vulnapi/report" "github.com/olekukonko/tablewriter" ) -type ScanVulnerabilityReport struct { +type ScanIssueReport struct { OperationMethod string `json:"method"` OperationPath string `json:"path"` - Vuln *report.VulnerabilityReport `json:"vuln"` + Issue *report.IssueReport `json:"issue"` } -type SortByPathAndSeverity []*ScanVulnerabilityReport +type SortByPathAndSeverity []*ScanIssueReport func (a SortByPathAndSeverity) Len() int { return len(a) } func (a SortByPathAndSeverity) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a SortByPathAndSeverity) Less(i, j int) bool { if a[i].OperationPath == a[j].OperationPath { if a[i].OperationMethod == a[j].OperationMethod { - return a[i].Vuln.Issue.CVSS.Score > a[j].Vuln.Issue.CVSS.Score + return a[i].Issue.CVSS.Score > a[j].Issue.CVSS.Score } return a[i].OperationMethod < a[j].OperationMethod @@ -32,30 +31,25 @@ func (a SortByPathAndSeverity) Less(i, j int) bool { return a[i].OperationPath < a[j].OperationPath } -func NewScanVulnerabilityReports(r *report.Report) []*ScanVulnerabilityReport { - vulnReports := r.GetFailedVulnerabilityReports() - vulns := make([]*ScanVulnerabilityReport, 0, len(vulnReports)) - for _, vulnReport := range vulnReports { - url, urlErr := url.Parse(r.Operation.URL) - if urlErr != nil { - continue - } - - vulns = append(vulns, &ScanVulnerabilityReport{ - OperationMethod: r.Operation.Method, - OperationPath: url.Path, +func NewScanIssueReports(r *report.ScanReport) []*ScanIssueReport { + reports := r.GetFailedIssueReports() + issues := make([]*ScanIssueReport, 0, len(reports)) + for _, ir := range reports { + issues = append(issues, &ScanIssueReport{ + OperationMethod: ir.Operation.Method, + OperationPath: ir.Operation.URL.Path, - Vuln: vulnReport, + Issue: ir, }) } - return vulns + return issues } -func NewFullScanVulnerabilityReports(reports []*report.Report) []*ScanVulnerabilityReport { - vulns := make([]*ScanVulnerabilityReport, 0) +func NewFullScanIssueReports(reports []*report.ScanReport) []*ScanIssueReport { + vulns := make([]*ScanIssueReport, 0) for _, r := range reports { - vulns = append(vulns, NewScanVulnerabilityReports(r)...) + vulns = append(vulns, NewScanIssueReports(r)...) } sort.Sort(SortByPathAndSeverity(vulns)) @@ -63,7 +57,7 @@ func NewFullScanVulnerabilityReports(reports []*report.Report) []*ScanVulnerabil return vulns } -func severityTableColor(v *report.VulnerabilityReport) int { +func severityTableColor(v *report.IssueReport) int { switch { case v.IsLowRiskSeverity() || v.IsInfoRiskSeverity(): return tablewriter.BgBlueColor @@ -79,7 +73,7 @@ func severityTableColor(v *report.VulnerabilityReport) int { } func DisplayReportSummaryTable(r *report.Reporter) { - if r == nil || len(r.GetReports()) == 0 { + if r == nil || len(r.GetScanReports()) == 0 { return } @@ -91,8 +85,8 @@ func DisplayReportSummaryTable(r *report.Reporter) { tableColors[0] = tablewriter.Colors{tablewriter.Bold} tableColors[1] = tablewriter.Colors{tablewriter.Bold} - for _, status := range report.VulnerabilityReportStatuses { - scansNumber := len(r.GetReportsByVulnerabilityStatus(status)) + for _, status := range report.IssueReportStatuses { + scansNumber := len(r.GetReportsByIssueStatus(status)) row := []string{ status.String(), @@ -107,27 +101,27 @@ func DisplayReportSummaryTable(r *report.Reporter) { } func DisplayReportTable(r *report.Reporter) { - if r == nil || !r.HasVulnerability() { + if r == nil || !r.HasIssue() { return } - headers := []string{"Operation", "Risk Level", "CVSS 4.0 Score", "OWASP", "Vulnerability"} + headers := []string{"Operation", "Risk Level", "CVSS 4.0 Score", "OWASP", "Issue"} table := CreateTable(headers) - vulnerabilityReports := NewFullScanVulnerabilityReports(r.GetReports()) - for _, vulnReport := range vulnerabilityReports { + IssueReports := NewFullScanIssueReports(r.GetScanReports()) + for _, issueReport := range IssueReports { row := []string{ - fmt.Sprintf("%s %s", vulnReport.OperationMethod, vulnReport.OperationPath), - vulnReport.Vuln.SeverityLevelString(), - fmt.Sprintf("%.1f", vulnReport.Vuln.Issue.CVSS.Score), - string(vulnReport.Vuln.Classifications.OWASP), - vulnReport.Vuln.Name, + fmt.Sprintf("%s %s", issueReport.OperationMethod, issueReport.OperationPath), + issueReport.Issue.SeverityLevelString(), + fmt.Sprintf("%.1f", issueReport.Issue.CVSS.Score), + string(issueReport.Issue.Classifications.OWASP), + issueReport.Issue.Name, } tableColors := make([]tablewriter.Colors, len(headers)) for i := range tableColors { if i == 1 { - tableColors[i] = tablewriter.Colors{tablewriter.Bold, severityTableColor(vulnReport.Vuln)} + tableColors[i] = tablewriter.Colors{tablewriter.Bold, severityTableColor(issueReport.Issue)} } else { tableColors[i] = tablewriter.Colors{} } diff --git a/internal/cmd/printtable/report_table_test.go b/internal/cmd/printtable/report_table_test.go index 621d414c..55d0fa70 100644 --- a/internal/cmd/printtable/report_table_test.go +++ b/internal/cmd/printtable/report_table_test.go @@ -4,13 +4,15 @@ import ( "testing" printtable "github.com/cerberauth/vulnapi/internal/cmd/printtable" + "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/report" "github.com/stretchr/testify/assert" ) -func TestNewScanVulnerabilityReports(t *testing.T) { - sr := &report.Report{ - Vulns: []*report.VulnerabilityReport{ +func TestNewScanIssueReports(t *testing.T) { + operation, _ := request.NewOperation("GET", "/api/v1/", nil, nil) + sr := &report.ScanReport{ + Issues: []*report.IssueReport{ { Issue: report.Issue{ Name: "Vuln1", @@ -18,7 +20,8 @@ func TestNewScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, { Issue: report.Issue{ @@ -27,30 +30,27 @@ func TestNewScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, }, - - Operation: report.ReportOperation{ - Method: "GET", - URL: "/api/v1/", - }, } - vulns := printtable.NewScanVulnerabilityReports(sr) + issues := printtable.NewScanIssueReports(sr) - assert.Len(t, vulns, 2) - assert.Equal(t, "GET", vulns[0].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[0].OperationPath) - assert.Equal(t, "Vuln1", vulns[0].Vuln.Issue.Name) - assert.Equal(t, "GET", vulns[1].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[1].OperationPath) - assert.Equal(t, "Vuln2", vulns[1].Vuln.Issue.Name) + assert.Len(t, issues, 2) + assert.Equal(t, "GET", issues[0].OperationMethod) + assert.Equal(t, "/api/v1/", issues[0].OperationPath) + assert.Equal(t, "Vuln1", issues[0].Issue.Name) + assert.Equal(t, "GET", issues[1].OperationMethod) + assert.Equal(t, "/api/v1/", issues[1].OperationPath) + assert.Equal(t, "Vuln2", issues[1].Issue.Name) } -func TestNewFullScanVulnerabilityReports(t *testing.T) { - sr1 := &report.Report{ - Vulns: []*report.VulnerabilityReport{ +func TestNewFullScanIssueReports(t *testing.T) { + operation, _ := request.NewOperation("GET", "/api/v1/", nil, nil) + sr1 := &report.ScanReport{ + Issues: []*report.IssueReport{ { Issue: report.Issue{ Name: "Vuln1", @@ -58,7 +58,8 @@ func TestNewFullScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, { Issue: report.Issue{ @@ -67,17 +68,13 @@ func TestNewFullScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, }, - - Operation: report.ReportOperation{ - Method: "GET", - URL: "/api/v1/", - }, } - sr2 := &report.Report{ - Vulns: []*report.VulnerabilityReport{ + sr2 := &report.ScanReport{ + Issues: []*report.IssueReport{ { Issue: report.Issue{ Name: "Vuln3", @@ -85,7 +82,8 @@ func TestNewFullScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, { Issue: report.Issue{ @@ -94,29 +92,29 @@ func TestNewFullScanVulnerabilityReports(t *testing.T) { Score: 5.0, }, }, - Status: report.VulnerabilityReportStatusFailed, + Status: report.IssueReportStatusFailed, + Operation: operation, }, }, - Operation: report.ReportOperation{ - Method: "GET", - URL: "/api/v1/", + Operation: &report.ScanReportOperation{ + ID: "id", }, } - vulns := printtable.NewFullScanVulnerabilityReports([]*report.Report{sr1, sr2}) + issues := printtable.NewFullScanIssueReports([]*report.ScanReport{sr1, sr2}) - assert.Len(t, vulns, 4) - assert.Equal(t, "GET", vulns[0].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[0].OperationPath) - assert.Equal(t, "Vuln1", vulns[0].Vuln.Issue.Name) - assert.Equal(t, "GET", vulns[1].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[1].OperationPath) - assert.Equal(t, "Vuln2", vulns[1].Vuln.Issue.Name) - assert.Equal(t, "GET", vulns[2].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[2].OperationPath) - assert.Equal(t, "Vuln3", vulns[2].Vuln.Issue.Name) - assert.Equal(t, "GET", vulns[3].OperationMethod) - assert.Equal(t, "/api/v1/", vulns[3].OperationPath) - assert.Equal(t, "Vuln4", vulns[3].Vuln.Issue.Name) + assert.Len(t, issues, 4) + assert.Equal(t, "GET", issues[0].OperationMethod) + assert.Equal(t, "/api/v1/", issues[0].OperationPath) + assert.Equal(t, "Vuln1", issues[0].Issue.Name) + assert.Equal(t, "GET", issues[1].OperationMethod) + assert.Equal(t, "/api/v1/", issues[1].OperationPath) + assert.Equal(t, "Vuln2", issues[1].Issue.Name) + assert.Equal(t, "GET", issues[2].OperationMethod) + assert.Equal(t, "/api/v1/", issues[2].OperationPath) + assert.Equal(t, "Vuln3", issues[2].Issue.Name) + assert.Equal(t, "GET", issues[3].OperationMethod) + assert.Equal(t, "/api/v1/", issues[3].OperationPath) + assert.Equal(t, "Vuln4", issues[3].Issue.Name) } diff --git a/internal/cmd/printtable/wellknown_paths_table.go b/internal/cmd/printtable/wellknown_paths_table.go index d29be4a0..6107b48d 100644 --- a/internal/cmd/printtable/wellknown_paths_table.go +++ b/internal/cmd/printtable/wellknown_paths_table.go @@ -11,7 +11,7 @@ import ( func WellKnownPathsScanReport(reporter *report.Reporter) { openapiURL := "" - openapiReport := reporter.GetReportByID(discoverableopenapi.DiscoverableOpenAPIScanID) + openapiReport := reporter.GetScanReportByID(discoverableopenapi.DiscoverableOpenAPIScanID) if openapiReport != nil && openapiReport.HasData() { openapiData, ok := openapiReport.Data.(discoverableopenapi.DiscoverableOpenAPIData) if ok { @@ -20,7 +20,7 @@ func WellKnownPathsScanReport(reporter *report.Reporter) { } graphqlURL := "" - graphqlReport := reporter.GetReportByID(discoverablegraphql.DiscoverableGraphQLPathScanID) + graphqlReport := reporter.GetScanReportByID(discoverablegraphql.DiscoverableGraphQLPathScanID) if graphqlReport != nil && graphqlReport.HasData() { graphqlData, ok := graphqlReport.Data.(discoverablegraphql.DiscoverableGraphQLPathData) if ok { diff --git a/internal/cmd/output.go b/internal/cmd/report.go similarity index 80% rename from internal/cmd/output.go rename to internal/cmd/report.go index d0310493..bcb5d98b 100644 --- a/internal/cmd/output.go +++ b/internal/cmd/report.go @@ -12,15 +12,15 @@ import ( func PrintOrExportReport(format string, transport string, report *report.Reporter) error { outputStream := os.Stdout - if report.HasHigherThanSeverityThresholdVulnerability(GetSeverityThreshold()) { + if report.HasHigherThanSeverityThresholdIssue(GetSeverityThreshold()) { outputStream = os.Stderr } var outputMessage string switch { - case !report.HasVulnerability(): - outputMessage = "Success: No vulnerabilities detected!" - case report.HasHighRiskOrHigherSeverityVulnerability(): + case !report.HasIssue(): + outputMessage = "Success: No issue detected!" + case report.HasHighRiskOrHigherSeverityIssue(): outputMessage = "Error: There are some high-risk issues. It's advised to take immediate action." default: outputMessage = "Warning: There are some issues. It's advised to take action." @@ -62,27 +62,25 @@ func PrintTable(report *report.Reporter) { } func ExportJSON(report *report.Reporter) ([]byte, error) { - reports := report.GetReports() - return json.Marshal(reports) + return json.Marshal(report) } func ExportYAML(report *report.Reporter) ([]byte, error) { - reports := report.GetReports() - return yaml.Marshal(reports) + return yaml.Marshal(report) } func exportWithTransport(transport string, output []byte) error { switch transport { case "file": - if outputPath == "" { + if reportFile == "" { return fmt.Errorf("output file is not specified") } - return writeFile(outputPath, output) + return writeFile(reportFile, output) case "http": - if outputURL == "" { + if reportURL == "" { return fmt.Errorf("output URL is not specified") } - return sendHTTP(outputURL, output) + return sendHTTP(reportURL, output) } return nil diff --git a/internal/request/operation.go b/internal/request/operation.go index e224e985..d222708c 100644 --- a/internal/request/operation.go +++ b/internal/request/operation.go @@ -7,35 +7,46 @@ import ( "net" "net/http" "net/url" + "regexp" + "strings" "github.com/cerberauth/vulnapi/internal/auth" "github.com/getkin/kin-openapi/openapi3" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) -type Operations []*Operation - -func (o Operations) Len() int { return len(o) } -func (o Operations) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o Operations) Less(i, j int) bool { - if o[i].URL == o[j].URL { - return o[i].Method < o[j].Method +func GenerateOperationID(method string, path string) string { + idSource := strings.ToLower(method) + pathParts := strings.Split(path, "/") + newPathParts := []string{} + caser := cases.Title(language.English) + for _, part := range pathParts { + if part != "" { + newPathParts = append(newPathParts, caser.String(part)) + } + } + if len(newPathParts) == 0 { + return idSource + "Root" } - return o[i].URL.String() < o[j].URL.String() + idSource += strings.Join(newPathParts, "") + re := regexp.MustCompile(`[^a-zA-Z0-9]+`) + return re.ReplaceAllString(idSource, "") } type Operation struct { *Client `json:"-" yaml:"-"` + OpenAPIDocPath *string `json:"-" yaml:"-"` + ID string `json:"id" yaml:"id"` + Method string `json:"method" yaml:"method"` URL url.URL `json:"url" yaml:"url"` Body *bytes.Buffer `json:"body,omitempty" yaml:"body,omitempty"` Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` Header http.Header `json:"header,omitempty" yaml:"header,omitempty"` - SecuritySchemes []auth.SecurityScheme `json:"security_schemes" yaml:"security_schemes"` - - ID string `json:"id" yaml:"id"` - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + SecuritySchemes []auth.SecurityScheme `json:"securitySchemes" yaml:"securitySchemes"` } func NewOperation(method string, operationUrl string, body *bytes.Buffer, client *Client) (*Operation, error) { @@ -89,7 +100,6 @@ func NewOperationFromRequest(r *Request) (*Operation, error) { return &Operation{ ID: r.URL.String(), - Tags: []string{}, Method: r.Method, URL: *r.URL, Body: &body, @@ -98,9 +108,14 @@ func NewOperationFromRequest(r *Request) (*Operation, error) { }, nil } -func (operation *Operation) WithOpenapiOperation(openapiOperation openapi3.Operation) *Operation { - operation.SetID(openapiOperation.OperationID) - operation.SetTags(openapiOperation.Tags) +func (operation *Operation) WithOpenapiOperation(docPath string, openapiOperation *openapi3.Operation) *Operation { + if openapiOperation.OperationID != "" { + operation.SetID(openapiOperation.OperationID) + } else { + operation.SetID(GenerateOperationID(operation.Method, docPath)) + } + operation.OpenAPIDocPath = &docPath + return operation } @@ -145,22 +160,22 @@ func (operation *Operation) GetPath() string { return operation.URL.Path } +func (operation *Operation) GetOpenAPIDocPath() *string { + return operation.OpenAPIDocPath +} + func (operation *Operation) SetID(id string) *Operation { operation.ID = id return operation } -func (operation *Operation) GetID() string { - return operation.ID -} - -func (operation *Operation) SetTags(tags []string) *Operation { - operation.Tags = tags +func (operation *Operation) GenerateID() *Operation { + operation.SetID(GenerateOperationID(operation.Method, operation.URL.Path)) return operation } -func (operation *Operation) GetTags() []string { - return operation.Tags +func (operation *Operation) GetID() string { + return operation.ID } func (o *Operation) Clone() *Operation { @@ -180,7 +195,6 @@ func (o *Operation) Clone() *Operation { Header: o.Header, SecuritySchemes: clonedSecuritySchemes, - ID: o.ID, - Tags: o.Tags, + ID: o.ID, } } diff --git a/internal/request/operation_test.go b/internal/request/operation_test.go index 1879eb1b..62cffbf6 100644 --- a/internal/request/operation_test.go +++ b/internal/request/operation_test.go @@ -105,16 +105,32 @@ func TestOperationCloneWithSecuritySchemes(t *testing.T) { } func TestOperation_WithOpenapiOperation(t *testing.T) { - operation := &request.Operation{} - openapiOperation := openapi3.Operation{ + operation, _ := request.NewOperation(http.MethodGet, "http://example.com", nil, nil) + openapiOperation := &openapi3.Operation{ OperationID: "testOperation", - Tags: []string{"tag1", "tag2"}, } - operation.WithOpenapiOperation(openapiOperation) + operation.WithOpenapiOperation("/", openapiOperation) assert.Equal(t, openapiOperation.OperationID, operation.GetID()) - assert.Equal(t, openapiOperation.Tags, operation.GetTags()) +} + +func TestOperation_WithOpenapiOperation_WithoutOperationID(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://example.com/resource", nil, nil) + openapiOperation := &openapi3.Operation{} + + operation.WithOpenapiOperation("/resource", openapiOperation) + + assert.Equal(t, "getResource", operation.GetID()) +} + +func TestOperation_WithOpenapiOperation_WithoutOperationIDAndParameters(t *testing.T) { + operation, _ := request.NewOperation(http.MethodGet, "http://example.com/resource", nil, nil) + openapiOperation := &openapi3.Operation{} + + operation.WithOpenapiOperation("/resource/{id}", openapiOperation) + + assert.Equal(t, "getResourceId", operation.GetID()) } func TestOperation_WithHeader(t *testing.T) { @@ -140,20 +156,38 @@ func TestOperation_WithCookies(t *testing.T) { assert.Equal(t, cookies, operation.Cookies) } -func TestOperation_SetId(t *testing.T) { - operation := &request.Operation{} +func TestOperation_GenerateID(t *testing.T) { + tests := []struct { + method string + url string + expected string + }{ + {http.MethodGet, "http://example.com/", "getRoot"}, + {http.MethodGet, "http://example.com/path/to/resource", "getPathToResource"}, + {http.MethodPost, "http://example.com/path/to/resource", "postPathToResource"}, + {http.MethodPut, "http://example.com/path/to/resource", "putPathToResource"}, + {http.MethodDelete, "http://example.com/path/to/resource", "deletePathToResource"}, + {http.MethodGet, "http://example.com/path/to/resource{[]}", "getPathToResource"}, + } - operation.SetID("testOperation") + for _, tt := range tests { + t.Run(tt.method+" "+tt.url, func(t *testing.T) { + operation, err := request.NewOperation(tt.method, tt.url, nil, nil) + assert.NoError(t, err) - assert.Equal(t, "testOperation", operation.GetID()) + operation.GenerateID() + + assert.Equal(t, tt.expected, operation.GetID()) + }) + } } -func TestOperation_SetTags(t *testing.T) { +func TestOperation_SetId(t *testing.T) { operation := &request.Operation{} - operation.SetTags([]string{"tag1", "tag2"}) + operation.SetID("testOperation") - assert.Equal(t, []string{"tag1", "tag2"}, operation.GetTags()) + assert.Equal(t, "testOperation", operation.GetID()) } func TestMarshalJSON(t *testing.T) { diff --git a/internal/request/operations.go b/internal/request/operations.go new file mode 100644 index 00000000..ac63936b --- /dev/null +++ b/internal/request/operations.go @@ -0,0 +1,22 @@ +package request + +type Operations []*Operation + +func (o Operations) Len() int { return len(o) } +func (o Operations) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o Operations) Less(i, j int) bool { + if o[i].URL == o[j].URL { + return o[i].Method < o[j].Method + } + + return o[i].URL.String() < o[j].URL.String() +} + +func (o Operations) GetByID(id string) *Operation { + for _, op := range o { + if op.GetID() == id { + return op + } + } + return nil +} diff --git a/internal/request/operations_test.go b/internal/request/operations_test.go new file mode 100644 index 00000000..bf8330e5 --- /dev/null +++ b/internal/request/operations_test.go @@ -0,0 +1,108 @@ +package request_test + +import ( + "net/http" + "testing" + + "github.com/cerberauth/vulnapi/internal/request" + "github.com/stretchr/testify/assert" +) + +func TestOperations_Less(t *testing.T) { + getOperation, _ := request.NewOperation(http.MethodGet, "http://example.com", nil, nil) + postOperation, _ := request.NewOperation(http.MethodPost, "http://example.com", nil, nil) + + tests := []struct { + name string + ops request.Operations + i, j int + expected bool + }{ + { + name: "Different URLs", + ops: request.Operations{ + getOperation, + postOperation, + }, + i: 0, + j: 1, + expected: true, + }, + { + name: "Same URLs, different methods", + ops: request.Operations{ + postOperation, + getOperation, + }, + i: 0, + j: 1, + expected: false, + }, + { + name: "Same URLs and methods", + ops: request.Operations{ + getOperation, + getOperation, + }, + i: 0, + j: 1, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.ops.Less(tt.i, tt.j) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestOperations_GetByID(t *testing.T) { + getOperation, _ := request.NewOperation(http.MethodGet, "http://example.com", nil, nil) + getOperation.SetID("1") + postOperation, _ := request.NewOperation(http.MethodPost, "http://example.com", nil, nil) + postOperation.SetID("2") + + tests := []struct { + name string + ops request.Operations + id string + expected *request.Operation + }{ + { + name: "Existing ID", + ops: request.Operations{ + getOperation, + postOperation, + }, + id: "1", + expected: getOperation, + }, + { + name: "Non-existing ID", + ops: request.Operations{ + getOperation, + postOperation, + }, + id: "3", + expected: nil, + }, + { + name: "Empty ID", + ops: request.Operations{ + getOperation, + postOperation, + }, + id: "", + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.ops.GetByID(tt.id) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/scan/scan_url.go b/internal/scan/scan_url.go index 0ffa660d..98c0300b 100644 --- a/internal/scan/scan_url.go +++ b/internal/scan/scan_url.go @@ -8,13 +8,13 @@ import ( "github.com/cerberauth/vulnapi/internal/request" ) -type VulnerabilityScanAttempt struct { +type IssueScanAttempt struct { Request *http.Request Response *http.Response Err error } -func ScanURL(operation *request.Operation, securityScheme *auth.SecurityScheme) (*VulnerabilityScanAttempt, error) { +func ScanURL(operation *request.Operation, securityScheme *auth.SecurityScheme) (*IssueScanAttempt, error) { req, err := operation.NewRequest() if err != nil { return nil, errors.New("request has an unexpected error") @@ -32,7 +32,7 @@ func ScanURL(operation *request.Operation, securityScheme *auth.SecurityScheme) } defer resp.Body.Close() - return &VulnerabilityScanAttempt{ + return &IssueScanAttempt{ Request: req.Request, Response: resp, Err: err, diff --git a/openapi/base_url.go b/openapi/base_url.go index b60e4456..bc44264d 100644 --- a/openapi/base_url.go +++ b/openapi/base_url.go @@ -9,7 +9,7 @@ func (openapi *OpenAPI) BaseUrl() *url.URL { return openapi.baseUrl } - for _, server := range openapi.doc.Servers { + for _, server := range openapi.Doc.Servers { if server.URL == "" { continue } diff --git a/openapi/openapi.go b/openapi/openapi.go index d86ec12f..55a32281 100644 --- a/openapi/openapi.go +++ b/openapi/openapi.go @@ -9,9 +9,9 @@ import ( type OpenAPI struct { baseUrl *url.URL - doc *openapi3.T + Doc *openapi3.T } func NewOpenAPI(doc *openapi3.T) *OpenAPI { - return &OpenAPI{doc: doc} + return &OpenAPI{Doc: doc} } diff --git a/openapi/operation.go b/openapi/operation.go index 3496c17b..3da03160 100644 --- a/openapi/operation.go +++ b/openapi/operation.go @@ -34,7 +34,7 @@ func getOperationSecuritySchemes(securityRequirements *openapi3.SecurityRequirem return operationsSecuritySchemes } -func getOperationPath(p string, params openapi3.Parameters) (string, error) { +func GetOperationPath(p string, params openapi3.Parameters) (string, error) { subs := map[string]interface{}{} for _, v := range params { if v.Value.In != "path" { @@ -51,9 +51,9 @@ func (openapi *OpenAPI) Operations(client *request.Client, securitySchemes auth. baseUrl := openapi.BaseUrl() operations := request.Operations{} - for docPath, p := range openapi.doc.Paths.Map() { + for docPath, p := range openapi.Doc.Paths.Map() { for method, o := range p.Operations() { - operationPath, err := getOperationPath(docPath, o.Parameters) + operationPath, err := GetOperationPath(docPath, o.Parameters) if err != nil { return nil, err } @@ -94,13 +94,13 @@ func (openapi *OpenAPI) Operations(client *request.Client, securitySchemes auth. if err != nil { return nil, err } - operation.WithOpenapiOperation(*o) + operation.WithOpenapiOperation(docPath, o) operation.WithCookies(cookies).WithHeader(header) if o.Security != nil { operation.SetSecuritySchemes(getOperationSecuritySchemes(o.Security, securitySchemes)) - } else if openapi.doc.Security != nil { - operation.SetSecuritySchemes(getOperationSecuritySchemes(&openapi.doc.Security, securitySchemes)) + } else if openapi.Doc.Security != nil { + operation.SetSecuritySchemes(getOperationSecuritySchemes(&openapi.Doc.Security, securitySchemes)) } operations = append(operations, operation) diff --git a/openapi/security_scheme.go b/openapi/security_scheme.go index d796b19c..2dcc29be 100644 --- a/openapi/security_scheme.go +++ b/openapi/security_scheme.go @@ -89,12 +89,12 @@ func (openapi *OpenAPI) SecuritySchemeMap(values *auth.SecuritySchemeValues) (au var err error var securitySchemeValue interface{} - if openapi.doc.Components == nil || openapi.doc.Components.SecuritySchemes == nil { + if openapi.Doc.Components == nil || openapi.Doc.Components.SecuritySchemes == nil { return nil, nil } securitySchemes := map[string]auth.SecurityScheme{} - for name, scheme := range openapi.doc.Components.SecuritySchemes { + for name, scheme := range openapi.Doc.Components.SecuritySchemes { securitySchemeValue = values.Get(name) var value *string diff --git a/report/curl_report.go b/report/curl_report.go new file mode 100644 index 00000000..c7f0068a --- /dev/null +++ b/report/curl_report.go @@ -0,0 +1,44 @@ +package report + +import ( + "net/http" + + "github.com/cerberauth/vulnapi/internal/auth" +) + +type CurlReport struct { + Method string `json:"method" yaml:"method"` + URL string `json:"url" yaml:"url"` + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` + Header http.Header `json:"headers,omitempty" yaml:"headers,omitempty"` + Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` + + SecuritySchemes []OperationSecurityScheme `json:"securitySchemes" yaml:"securitySchemes"` + + Issues []*IssueReport `json:"issues" yaml:"issues"` +} + +func NewCurlReport(method string, url string, data interface{}, header http.Header, cookies []*http.Cookie, securitySchemes []auth.SecurityScheme) *CurlReport { + reportSecuritySchemes := []OperationSecurityScheme{} + for _, ss := range securitySchemes { + reportSecuritySchemes = append(reportSecuritySchemes, NewOperationSecurityScheme(ss)) + } + + return &CurlReport{ + Method: method, + URL: url, + Data: data, + Header: header, + Cookies: cookies, + + SecuritySchemes: reportSecuritySchemes, + + Issues: []*IssueReport{}, + } +} + +func (cr *CurlReport) AddReport(r *ScanReport) { + if r.HasFailedIssueReport() { + cr.Issues = append(cr.Issues, r.GetFailedIssueReports()...) + } +} diff --git a/report/curl_report_test.go b/report/curl_report_test.go new file mode 100644 index 00000000..98c70bec --- /dev/null +++ b/report/curl_report_test.go @@ -0,0 +1,88 @@ +package report_test + +import ( + "net/http" + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/jwt" + "github.com/cerberauth/vulnapi/report" + "github.com/stretchr/testify/assert" +) + +func TestNewCurlReport(t *testing.T) { + method := http.MethodGet + url := "http://example.com" + data := map[string]interface{}{"key": "value"} + header := http.Header{"Content-Type": []string{"application/json"}} + cookies := []*http.Cookie{{Name: "session_id", Value: "abc123"}} + value := jwt.FakeJWT + securityScheme, _ := auth.NewAuthorizationJWTBearerSecurityScheme("token", &value) + securitySchemes := []auth.SecurityScheme{securityScheme} + + curlReport := report.NewCurlReport(method, url, data, header, cookies, securitySchemes) + + assert.Equal(t, method, curlReport.Method) + assert.Equal(t, url, curlReport.URL) + assert.Equal(t, data, curlReport.Data) + assert.Equal(t, header, curlReport.Header) + assert.Equal(t, cookies, curlReport.Cookies) + assert.Len(t, curlReport.SecuritySchemes, len(securitySchemes)) + assert.Equal(t, auth.HttpType, curlReport.SecuritySchemes[0].Type) + assert.Equal(t, auth.InHeader, *curlReport.SecuritySchemes[0].In) + assert.Equal(t, "token", curlReport.SecuritySchemes[0].Name) + assert.Empty(t, curlReport.Issues) +} + +func Test_CurlReport_AddReport(t *testing.T) { + method := http.MethodGet + url := "http://example.com" + data := map[string]interface{}{"key": "value"} + header := http.Header{"Content-Type": []string{"application/json"}} + cookies := []*http.Cookie{{Name: "session_id", Value: "abc123"}} + securitySchemes := []auth.SecurityScheme{} + + curlReport := report.NewCurlReport(method, url, data, header, cookies, securitySchemes) + + operation, _ := request.NewOperation(method, url, nil, nil) + scanReport := report.NewScanReport("id", "test", operation) + firstIssue := report.Issue{ + Name: "issue 1", + } + secondIssue := report.Issue{ + Name: "issue 2", + } + firstIssueReport := report.NewIssueReport(firstIssue).Fail() + secondIssueReport := report.NewIssueReport(secondIssue).Fail() + scanReport.AddIssueReport(firstIssueReport).AddIssueReport(secondIssueReport) + + curlReport.AddReport(scanReport) + + assert.Len(t, curlReport.Issues, 2) + assert.Equal(t, firstIssue.Name, curlReport.Issues[0].Name) + assert.Equal(t, secondIssue.Name, curlReport.Issues[1].Name) +} + +func TestAddReport_WhenScanReportHasNoFailedIssueReport(t *testing.T) { + method := http.MethodGet + url := "http://example.com" + data := map[string]interface{}{"key": "value"} + header := http.Header{"Content-Type": []string{"application/json"}} + cookies := []*http.Cookie{{Name: "session_id", Value: "abc123"}} + securitySchemes := []auth.SecurityScheme{} + + curlReport := report.NewCurlReport(method, url, data, header, cookies, securitySchemes) + + operation, _ := request.NewOperation(method, url, nil, nil) + scanReport := report.NewScanReport("id", "test", operation) + issue := report.Issue{ + Name: "issue 1", + } + issueReport := report.NewIssueReport(issue).Pass() + scanReport.AddIssueReport(issueReport) + + curlReport.AddReport(scanReport) + + assert.Empty(t, curlReport.Issues) +} diff --git a/report/graphql_report.go b/report/graphql_report.go new file mode 100644 index 00000000..d1f46ee3 --- /dev/null +++ b/report/graphql_report.go @@ -0,0 +1,53 @@ +package report + +import "github.com/cerberauth/vulnapi/internal/auth" + +type GraphQLOperationReport struct { + ID string `json:"id" yaml:"id"` + Tags []string `json:"tags" yaml:"tags"` + + SecuritySchemes []OperationSecurityScheme `json:"securitySchemes" yaml:"securitySchemes"` + Issues []*IssueReport `json:"issues" yaml:"issues"` +} + +func NewGraphQLOperationReport() GraphQLOperationReport { + return GraphQLOperationReport{ + SecuritySchemes: []OperationSecurityScheme{}, // TODO + + Issues: []*IssueReport{}, + } +} + +type GraphQLOperationsMethods map[string]GraphQLOperationReport +type GraphQLReport struct { + URL string `json:"url" yaml:"url"` + + Queries GraphQLOperationsMethods `json:"queries" yaml:"queries"` + Mutations GraphQLOperationsMethods `json:"mutations" yaml:"mutations"` +} + +func NewGraphQLReport(url string, securitySchemes []auth.SecurityScheme) *GraphQLReport { + queries := GraphQLOperationsMethods{} + mutations := GraphQLOperationsMethods{} + + return &GraphQLReport{ + URL: url, + + Queries: queries, + Mutations: mutations, + } +} + +func (gr *GraphQLReport) AddReport(r *ScanReport) { + for _, operation := range gr.Queries { + if operation.ID == r.Operation.ID { + operation.Issues = append(operation.Issues, r.Issues...) + } + } + + for _, operation := range gr.Mutations { + if operation.ID == r.Operation.ID { + operation.Issues = append(operation.Issues, r.Issues...) + } + } +} diff --git a/report/issue.go b/report/issue.go index 8aa7bcc1..c2f90978 100644 --- a/report/issue.go +++ b/report/issue.go @@ -15,7 +15,7 @@ type CVSS struct { type Issue struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` - URL string `json:"url" yaml:"url"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` CVSS CVSS `json:"cvss" yaml:"cvss"` Classifications *Classifications `json:"classifications,omitempty" yaml:"classifications,omitempty"` diff --git a/report/issue_report.go b/report/issue_report.go new file mode 100644 index 00000000..777a1864 --- /dev/null +++ b/report/issue_report.go @@ -0,0 +1,138 @@ +package report + +import ( + "fmt" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" +) + +type IssueReportStatus string + +func (vrs IssueReportStatus) String() string { + return string(vrs) +} + +const ( + IssueReportStatusPassed IssueReportStatus = "passed" + IssueReportStatusFailed IssueReportStatus = "failed" + IssueReportStatusSkipped IssueReportStatus = "skipped" + IssueReportStatusNone IssueReportStatus = "none" +) + +var IssueReportStatuses = []IssueReportStatus{ + IssueReportStatusPassed, + IssueReportStatusFailed, + IssueReportStatusSkipped, + IssueReportStatusNone, +} + +type IssueReport struct { + Issue `json:",inline" yaml:",inline"` + Status IssueReportStatus `json:"status" yaml:"status"` + + Operation *request.Operation `json:"-" yaml:"-"` + SecurityScheme auth.SecurityScheme `json:"-" yaml:"-"` +} + +func NewIssueReport(issue Issue) *IssueReport { + return &IssueReport{ + Issue: issue, + + Status: IssueReportStatusNone, + } +} + +func (vr *IssueReport) WithOperation(operation *request.Operation) *IssueReport { + vr.Operation = operation + return vr +} + +func (vr *IssueReport) WithSecurityScheme(ss auth.SecurityScheme) *IssueReport { + vr.SecurityScheme = ss + return vr +} + +func (vr *IssueReport) WithStatus(status IssueReportStatus) *IssueReport { + vr.Status = status + return vr +} + +func (vr *IssueReport) WithBooleanStatus(status bool) *IssueReport { + if status { + return vr.Pass() + } + return vr.Fail() +} + +func (vr *IssueReport) Fail() *IssueReport { + vr.Status = IssueReportStatusFailed + return vr +} + +func (vr *IssueReport) HasFailed() bool { + return vr.Status == IssueReportStatusFailed +} + +func (vr *IssueReport) Pass() *IssueReport { + vr.Status = IssueReportStatusPassed + return vr +} + +func (vr *IssueReport) HasPassed() bool { + return vr.Status == IssueReportStatusPassed +} + +func (vr *IssueReport) Skip() *IssueReport { + vr.Status = IssueReportStatusSkipped + return vr +} + +func (vr *IssueReport) HasBeenSkipped() bool { + return vr.Status == IssueReportStatusSkipped +} + +func (vr *IssueReport) IsInfoRiskSeverity() bool { + return vr.CVSS.Score == 0 +} + +func (vr *IssueReport) IsLowRiskSeverity() bool { + return vr.CVSS.Score < 4 && vr.CVSS.Score > 0 +} + +func (vr *IssueReport) IsMediumRiskSeverity() bool { + return vr.CVSS.Score > 4 && vr.CVSS.Score < 7 +} + +func (vr *IssueReport) IsHighRiskSeverity() bool { + return vr.CVSS.Score > 7 && vr.CVSS.Score < 9 +} + +func (vr *IssueReport) IsCriticalRiskSeverity() bool { + return vr.CVSS.Score > 9 +} + +func (vr *IssueReport) String() string { + return fmt.Sprintf("[%s] %s", vr.SeverityLevelString(), vr.Name) +} + +func (vr *IssueReport) SeverityLevelString() string { + switch { + case vr.IsCriticalRiskSeverity(): + return "Critical" + case vr.IsHighRiskSeverity(): + return "High" + case vr.IsMediumRiskSeverity(): + return "Medium" + case vr.IsLowRiskSeverity(): + return "Low" + case vr.IsInfoRiskSeverity(): + return "Info" + default: + return "None" + } +} + +func (vr *IssueReport) Clone() *IssueReport { + return NewIssueReport(vr.Issue).WithOperation(vr.Operation).WithSecurityScheme(vr.SecurityScheme).WithStatus(vr.Status) +} diff --git a/report/vuln_test.go b/report/issue_report_test.go similarity index 65% rename from report/vuln_test.go rename to report/issue_report_test.go index b6a34008..5a21b878 100644 --- a/report/vuln_test.go +++ b/report/issue_report_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewVulnerabilityReport(t *testing.T) { +func TestNewIssueReport(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -19,14 +19,14 @@ func TestNewVulnerabilityReport(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) assert.Equal(t, "id", vr.ID) assert.Equal(t, "Test Vulnerability", vr.Name) assert.Equal(t, "http://test.com", vr.URL) assert.Equal(t, 7.5, vr.CVSS.Score) } -func TestVulnerabilityReport_WithOperation(t *testing.T) { +func TestIssueReport_WithOperation(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -35,14 +35,14 @@ func TestVulnerabilityReport_WithOperation(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) operation, _ := request.NewOperation("GET", "/api/v1/", nil, nil) vr.WithOperation(operation) assert.Equal(t, "GET", vr.Operation.Method) assert.Equal(t, "/api/v1/", vr.Operation.URL.Path) } -func TestVulnerabilityReport_WithSecurityScheme(t *testing.T) { +func TestIssueReport_WithSecurityScheme(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -51,14 +51,14 @@ func TestVulnerabilityReport_WithSecurityScheme(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) value := jwt.FakeJWT securityScheme, _ := auth.NewAuthorizationJWTBearerSecurityScheme("token", &value) vr.WithSecurityScheme(securityScheme) assert.Equal(t, jwt.FakeJWT, vr.SecurityScheme.GetValidValue()) } -func TestVulnerabilityReport_WithStatus(t *testing.T) { +func TestIssueReport_WithStatus(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -67,12 +67,12 @@ func TestVulnerabilityReport_WithStatus(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) - vr.WithStatus(report.VulnerabilityReportStatusFailed) - assert.Equal(t, report.VulnerabilityReportStatusFailed, vr.Status) + vr := report.NewIssueReport(issue) + vr.WithStatus(report.IssueReportStatusFailed) + assert.Equal(t, report.IssueReportStatusFailed, vr.Status) } -func TestVulnerabilityReport_WithBooleanStatus_WhenFalse(t *testing.T) { +func TestIssueReport_WithBooleanStatus_WhenFalse(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -81,12 +81,12 @@ func TestVulnerabilityReport_WithBooleanStatus_WhenFalse(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.WithBooleanStatus(false) - assert.Equal(t, report.VulnerabilityReportStatusFailed, vr.Status) + assert.Equal(t, report.IssueReportStatusFailed, vr.Status) } -func TestVulnerabilityReport_WithBooleanStatus_WhenTrue(t *testing.T) { +func TestIssueReport_WithBooleanStatus_WhenTrue(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -95,12 +95,12 @@ func TestVulnerabilityReport_WithBooleanStatus_WhenTrue(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.WithBooleanStatus(true) - assert.Equal(t, report.VulnerabilityReportStatusPassed, vr.Status) + assert.Equal(t, report.IssueReportStatusPassed, vr.Status) } -func TestVulnerabilityReport_Fail(t *testing.T) { +func TestIssueReport_Fail(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -109,12 +109,12 @@ func TestVulnerabilityReport_Fail(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Fail() - assert.Equal(t, report.VulnerabilityReportStatusFailed, vr.Status) + assert.Equal(t, report.IssueReportStatusFailed, vr.Status) } -func TestVulnerabilityReport_HasFailed(t *testing.T) { +func TestIssueReport_HasFailed(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -123,12 +123,12 @@ func TestVulnerabilityReport_HasFailed(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Fail() assert.True(t, vr.HasFailed()) } -func TestVulnerabilityReport_Pass(t *testing.T) { +func TestIssueReport_Pass(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -137,12 +137,12 @@ func TestVulnerabilityReport_Pass(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Pass() - assert.Equal(t, report.VulnerabilityReportStatusPassed, vr.Status) + assert.Equal(t, report.IssueReportStatusPassed, vr.Status) } -func TestVulnerabilityReport_HasPassed(t *testing.T) { +func TestIssueReport_HasPassed(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -151,12 +151,12 @@ func TestVulnerabilityReport_HasPassed(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Pass() assert.True(t, vr.HasPassed()) } -func TestVulnerabilityReport_Skip(t *testing.T) { +func TestIssueReport_Skip(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -165,12 +165,12 @@ func TestVulnerabilityReport_Skip(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Skip() - assert.Equal(t, report.VulnerabilityReportStatusSkipped, vr.Status) + assert.Equal(t, report.IssueReportStatusSkipped, vr.Status) } -func TestVulnerabilityReport_HasBeenSkipped(t *testing.T) { +func TestIssueReport_HasBeenSkipped(t *testing.T) { issue := report.Issue{ ID: "id", Name: "Test Vulnerability", @@ -179,13 +179,13 @@ func TestVulnerabilityReport_HasBeenSkipped(t *testing.T) { Score: 7.5, }, } - vr := report.NewVulnerabilityReport(issue) + vr := report.NewIssueReport(issue) vr.Skip() assert.True(t, vr.HasBeenSkipped()) } -func TestVulnerabilityReport_IsInfoRiskSeverity(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_IsInfoRiskSeverity(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{ Score: 0, @@ -195,8 +195,8 @@ func TestVulnerabilityReport_IsInfoRiskSeverity(t *testing.T) { assert.True(t, vr.IsInfoRiskSeverity()) } -func TestVulnerabilityReport_IsLowRiskSeverity(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_IsLowRiskSeverity(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{ Score: 3.5, @@ -206,8 +206,8 @@ func TestVulnerabilityReport_IsLowRiskSeverity(t *testing.T) { assert.True(t, vr.IsLowRiskSeverity()) } -func TestVulnerabilityReport_IsMediumRiskSeverity(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_IsMediumRiskSeverity(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{ Score: 5.5, @@ -217,8 +217,8 @@ func TestVulnerabilityReport_IsMediumRiskSeverity(t *testing.T) { assert.True(t, vr.IsMediumRiskSeverity()) } -func TestVulnerabilityReport_IsHighRiskSeverity(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_IsHighRiskSeverity(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{ Score: 8.5, @@ -228,8 +228,8 @@ func TestVulnerabilityReport_IsHighRiskSeverity(t *testing.T) { assert.True(t, vr.IsHighRiskSeverity()) } -func TestVulnerabilityReport_IsCriticalRiskSeverity(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_IsCriticalRiskSeverity(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{ Score: 9.5, @@ -239,8 +239,8 @@ func TestVulnerabilityReport_IsCriticalRiskSeverity(t *testing.T) { assert.True(t, vr.IsCriticalRiskSeverity()) } -func TestVulnerabilityReport_String(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_String(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ Name: "Test Vulnerability", @@ -253,8 +253,8 @@ func TestVulnerabilityReport_String(t *testing.T) { assert.Equal(t, expected, vr.String()) } -func TestVulnerabilityReport_SeverityLevelString(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_SeverityLevelString(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ CVSS: report.CVSS{}, }, @@ -281,8 +281,8 @@ func TestVulnerabilityReport_SeverityLevelString(t *testing.T) { assert.Equal(t, "None", vr.SeverityLevelString()) } -func TestVulnerabilityReport_Clone(t *testing.T) { - vr := &report.VulnerabilityReport{ +func TestIssueReport_Clone(t *testing.T) { + vr := &report.IssueReport{ Issue: report.Issue{ Name: "Test Vulnerability", URL: "http://test.com", diff --git a/report/openapi_report.go b/report/openapi_report.go new file mode 100644 index 00000000..5a2f6a59 --- /dev/null +++ b/report/openapi_report.go @@ -0,0 +1,90 @@ +package report + +import ( + "github.com/cerberauth/vulnapi/internal/request" + "github.com/getkin/kin-openapi/openapi3" +) + +func findOperationByMethodAndPath(operations request.Operations, method string, path string) *request.Operation { + for _, operation := range operations { + if operation.Method == method && operation.GetOpenAPIDocPath() != nil && *operation.GetOpenAPIDocPath() == path { + return operation + } + } + + return nil +} + +type OpenAPIReportOperation struct { + ID string `json:"operationId" yaml:"operationId"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + + SecuritySchemes []OperationSecurityScheme `json:"securitySchemes" yaml:"securitySchemes"` + + Issues []*IssueReport `json:"issues" yaml:"issues"` +} + +func NewOpenAPIReportOperation(operation *openapi3.Operation, requestOperation *request.Operation) OpenAPIReportOperation { + reportSecuritySchemes := []OperationSecurityScheme{} + for _, ss := range requestOperation.GetSecuritySchemes() { + reportSecuritySchemes = append(reportSecuritySchemes, NewOperationSecurityScheme(ss)) + } + + return OpenAPIReportOperation{ + ID: requestOperation.GetID(), + Tags: operation.Tags, + + SecuritySchemes: reportSecuritySchemes, + + Issues: []*IssueReport{}, + } +} + +type OpenAPIReportMethods map[string]OpenAPIReportOperation +type OpenAPIReportPaths map[string]OpenAPIReportMethods +type OpenAPIReport struct { + Paths OpenAPIReportPaths `json:"paths" yaml:"paths"` +} + +func NewOpenAPIReport(doc *openapi3.T, operations request.Operations) *OpenAPIReport { + paths := OpenAPIReportPaths{} + for docPath, p := range doc.Paths.Map() { + paths[docPath] = OpenAPIReportMethods{} + for method, o := range p.Operations() { + var requestOperation *request.Operation + if o.OperationID != "" { + requestOperation = operations.GetByID(o.OperationID) + } + + if requestOperation == nil { + requestOperation = findOperationByMethodAndPath(operations, method, docPath) + } + + if requestOperation == nil { + continue + } + + openAPIOperation := NewOpenAPIReportOperation(o, requestOperation) + paths[docPath][method] = openAPIOperation + } + } + + return &OpenAPIReport{ + Paths: paths, + } +} + +func (or *OpenAPIReport) AddReport(r *ScanReport) { + if r == nil || !r.HasFailedIssueReport() || r.Operation == nil { + return + } + + for path, methods := range or.Paths { + for method, operationReport := range methods { + if operationReport.ID == r.Operation.ID { + operationReport.Issues = append(operationReport.Issues, r.GetFailedIssueReports()...) + or.Paths[path][method] = operationReport + } + } + } +} diff --git a/report/openapi_report_test.go b/report/openapi_report_test.go new file mode 100644 index 00000000..84fcd578 --- /dev/null +++ b/report/openapi_report_test.go @@ -0,0 +1,87 @@ +package report_test + +import ( + "context" + "net/http" + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/openapi" + "github.com/cerberauth/vulnapi/report" + "github.com/stretchr/testify/assert" +) + +func TestNewOpenAPIReportOperation(t *testing.T) { + doc, _ := openapi.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := doc.SecuritySchemeMap(&auth.SecuritySchemeValues{}) + operations, _ := doc.Operations(nil, securitySchemesMap) + securitySchemes := operations[0].GetSecuritySchemes() + + o := operations.GetByID("getRoot") + + r := report.NewOpenAPIReportOperation(doc.Doc.Paths.Map()["/"].Get, o) + + assert.NotNil(t, r) + assert.Equal(t, "getRoot", r.ID) + assert.Equal(t, report.NewOperationSecurityScheme(securitySchemes[0]), r.SecuritySchemes[0]) + assert.Equal(t, []*report.IssueReport{}, r.Issues) +} + +func TestNewOpenAPIReport(t *testing.T) { + doc, _ := openapi.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := doc.SecuritySchemeMap(&auth.SecuritySchemeValues{}) + operations, _ := doc.Operations(nil, securitySchemesMap) + + r := report.NewOpenAPIReport(doc.Doc, operations) + + assert.NotNil(t, r) + assert.Contains(t, r.Paths, "/") + assert.Contains(t, r.Paths["/"], http.MethodGet) + assert.Equal(t, "getRoot", r.Paths["/"][http.MethodGet].ID) + assert.Equal(t, []*report.IssueReport{}, r.Paths["/"][http.MethodGet].Issues) +} + +func Test_OpenAPIReport_AddReport(t *testing.T) { + doc, _ := openapi.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := doc.SecuritySchemeMap(&auth.SecuritySchemeValues{}) + operations, _ := doc.Operations(nil, securitySchemesMap) + + r := report.NewOpenAPIReport(doc.Doc, operations) + + scanReport := report.NewScanReport("id", "test", operations[0]) + issue := report.Issue{ + Name: "issue 1", + } + issueReport := report.NewIssueReport(issue).Fail() + scanReport.AddIssueReport(issueReport) + r.AddReport(scanReport) + + assert.NotNil(t, r) + assert.Contains(t, r.Paths, "/") + assert.Contains(t, r.Paths["/"], http.MethodGet) + assert.Equal(t, "getRoot", r.Paths["/"][http.MethodGet].ID) + assert.Equal(t, 1, len(r.Paths["/"][http.MethodGet].Issues)) + assert.Equal(t, issueReport, r.Paths["/"][http.MethodGet].Issues[0]) +} + +func Test_OpenAPIReport_AddReport_NoFailedIssue(t *testing.T) { + doc, _ := openapi.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := doc.SecuritySchemeMap(&auth.SecuritySchemeValues{}) + operations, _ := doc.Operations(nil, securitySchemesMap) + + r := report.NewOpenAPIReport(doc.Doc, operations) + + scanReport := report.NewScanReport("id", "test", operations[0]) + issue := report.Issue{ + Name: "issue 1", + } + issueReport := report.NewIssueReport(issue).Pass() + scanReport.AddIssueReport(issueReport) + r.AddReport(scanReport) + + assert.NotNil(t, r) + assert.Contains(t, r.Paths, "/") + assert.Contains(t, r.Paths["/"], http.MethodGet) + assert.Equal(t, "getRoot", r.Paths["/"][http.MethodGet].ID) + assert.Equal(t, 0, len(r.Paths["/"][http.MethodGet].Issues)) +} diff --git a/report/options_report.go b/report/options_report.go new file mode 100644 index 00000000..5773b281 --- /dev/null +++ b/report/options_report.go @@ -0,0 +1,7 @@ +package report + +type OptionsReport struct{} // TODO + +func NewOptionsReport() OptionsReport { + return OptionsReport{} +} diff --git a/report/report.go b/report/report.go index 90165515..262ada60 100644 --- a/report/report.go +++ b/report/report.go @@ -9,119 +9,106 @@ import ( "github.com/cerberauth/vulnapi/internal/scan" ) -type ReportRequest struct { - Method string `json:"method" yaml:"method"` - URL string `json:"url" yaml:"url"` - Body *string `json:"body,omitempty" yaml:"body,omitempty"` - Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` - Header http.Header `json:"headers,omitempty" yaml:"headers,omitempty"` -} - -type ReportResponse struct { - StatusCode int `json:"statusCode" yaml:"statusCode"` - Body string `json:"body" yaml:"body"` - Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` - Header http.Header `json:"headers,omitempty" yaml:"headers,omitempty"` -} - -type ReportScan struct { - Request *ReportRequest `json:"request,omitempty" yaml:"request,omitempty"` - Response *ReportResponse `json:"response,omitempty" yaml:"response,omitempty"` - Err error `json:"error,omitempty" yaml:"error,omitempty"` -} - -type ReportOperationSecurityScheme struct { +type OperationSecurityScheme struct { Type auth.Type `json:"type" yaml:"type"` Scheme auth.SchemeName `json:"scheme" yaml:"scheme"` In *auth.SchemeIn `json:"in,omitempty" yaml:"in,omitempty"` Name string `json:"name" yaml:"name"` } -type ReportOperation struct { - ID string `json:"id" yaml:"id"` - Tags []string `json:"tags" yaml:"tags"` +func NewOperationSecurityScheme(ss auth.SecurityScheme) OperationSecurityScheme { + return OperationSecurityScheme{ + Type: ss.GetType(), + Scheme: ss.GetScheme(), + In: ss.GetIn(), + Name: ss.GetName(), + } +} +type ScanReportRequest struct { Method string `json:"method" yaml:"method"` URL string `json:"url" yaml:"url"` + Body *string `json:"body,omitempty" yaml:"body,omitempty"` Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` Header http.Header `json:"headers,omitempty" yaml:"headers,omitempty"` +} + +type ScanReportResponse struct { + StatusCode int `json:"status_code" yaml:"status_code"` + Body *string `json:"body,omitempty" yaml:"body,omitempty"` + Cookies []*http.Cookie `json:"cookies,omitempty" yaml:"cookies,omitempty"` + Header http.Header `json:"headers,omitempty" yaml:"headers,omitempty"` +} - SecuritySchemes []ReportOperationSecurityScheme `json:"securitySchemes" yaml:"securitySchemes"` +type ScanReportScan struct { + Request *ScanReportRequest `json:"request,omitempty" yaml:"request,omitempty"` + Response *ScanReportResponse `json:"response,omitempty" yaml:"response,omitempty"` + Err error `json:"error,omitempty" yaml:"error,omitempty"` } -type Report struct { +type ScanReportOperation struct { + ID string `json:"id" yaml:"id"` +} +type ScanReport struct { ID string `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` StartTime time.Time `json:"startTime" yaml:"startTime"` EndTime time.Time `json:"endTime,omitempty" yaml:"endTime,omitempty"` - Operation ReportOperation `json:"operation" yaml:"operation"` + Operation *ScanReportOperation `json:"operation,omitempty" yaml:"operation,omitempty"` - Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` - Scans []ReportScan `json:"scans" yaml:"scans"` - Vulns []*VulnerabilityReport `json:"vulnerabilities" yaml:"vulnerabilities"` + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` + Scans []ScanReportScan `json:"scans" yaml:"scans"` + Issues []*IssueReport `json:"issues" yaml:"issues"` } -func NewScanReport(id string, name string, operation *request.Operation) *Report { - securitySchemes := []ReportOperationSecurityScheme{} - for _, ss := range operation.SecuritySchemes { - securitySchemes = append(securitySchemes, ReportOperationSecurityScheme{ - Type: ss.GetType(), - Scheme: ss.GetScheme(), - In: ss.GetIn(), - Name: ss.GetName(), - }) +func NewScanReport(id string, name string, operation *request.Operation) *ScanReport { + var scanOperation *ScanReportOperation + if operation != nil && operation.ID != "" { + scanOperation = &ScanReportOperation{ + ID: operation.ID, + } } - return &Report{ + return &ScanReport{ ID: id, Name: name, StartTime: time.Now(), - Operation: ReportOperation{ - ID: operation.ID, - Tags: operation.Tags, - - Method: operation.Method, - URL: operation.URL.String(), - Cookies: operation.Cookies, - Header: operation.Header, - - SecuritySchemes: securitySchemes, - }, + Operation: scanOperation, - Scans: []ReportScan{}, - Vulns: []*VulnerabilityReport{}, + Scans: []ScanReportScan{}, + Issues: []*IssueReport{}, } } -func (r *Report) Start() *Report { +func (r *ScanReport) Start() *ScanReport { r.StartTime = time.Now() return r } -func (r *Report) End() *Report { +func (r *ScanReport) End() *ScanReport { r.EndTime = time.Now() return r } -func (r *Report) WithData(data interface{}) *Report { +func (r *ScanReport) WithData(data interface{}) *ScanReport { r.Data = data return r } -func (r *Report) GetData() interface{} { +func (r *ScanReport) GetData() interface{} { return r.Data } -func (r *Report) HasData() bool { +func (r *ScanReport) HasData() bool { return r.Data != nil } -func (r *Report) AddScanAttempt(a *scan.VulnerabilityScanAttempt) *Report { - var reportRequest *ReportRequest = nil +func (r *ScanReport) AddScanAttempt(a *scan.IssueScanAttempt) *ScanReport { + var reportRequest *ScanReportRequest = nil if a.Request != nil { - reportRequest = &ReportRequest{ + reportRequest = &ScanReportRequest{ Method: a.Request.Method, URL: a.Request.URL.String(), Cookies: a.Request.Cookies(), @@ -129,16 +116,16 @@ func (r *Report) AddScanAttempt(a *scan.VulnerabilityScanAttempt) *Report { } } - var reportResponse *ReportResponse = nil + var reportResponse *ScanReportResponse = nil if a.Response != nil { - reportResponse = &ReportResponse{ + reportResponse = &ScanReportResponse{ StatusCode: a.Response.StatusCode, Cookies: a.Response.Cookies(), Header: a.Response.Header, } } - r.Scans = append(r.Scans, ReportScan{ + r.Scans = append(r.Scans, ScanReportScan{ Request: reportRequest, Response: reportResponse, Err: a.Err, @@ -146,20 +133,20 @@ func (r *Report) AddScanAttempt(a *scan.VulnerabilityScanAttempt) *Report { return r } -func (r *Report) GetScanAttempts() []ReportScan { +func (r *ScanReport) GetScanAttempts() []ScanReportScan { return r.Scans } -func (r *Report) AddVulnerabilityReport(vr *VulnerabilityReport) *Report { - r.Vulns = append(r.Vulns, vr) +func (r *ScanReport) AddIssueReport(vr *IssueReport) *ScanReport { + r.Issues = append(r.Issues, vr) return r } -func (r *Report) GetVulnerabilityReports() []*VulnerabilityReport { - return r.Vulns +func (r *ScanReport) GetIssueReports() []*IssueReport { + return r.Issues } -func (r *Report) GetErrors() []error { +func (r *ScanReport) GetErrors() []error { var errors []error for _, sa := range r.GetScanAttempts() { if sa.Err != nil { @@ -169,9 +156,9 @@ func (r *Report) GetErrors() []error { return errors } -func (r *Report) GetFailedVulnerabilityReports() []*VulnerabilityReport { - var failedReports []*VulnerabilityReport - for _, vr := range r.GetVulnerabilityReports() { +func (r *ScanReport) GetFailedIssueReports() []*IssueReport { + var failedReports []*IssueReport + for _, vr := range r.GetIssueReports() { if vr.HasFailed() { failedReports = append(failedReports, vr) } @@ -179,6 +166,6 @@ func (r *Report) GetFailedVulnerabilityReports() []*VulnerabilityReport { return failedReports } -func (r *Report) HasFailedVulnerabilityReport() bool { - return len(r.GetFailedVulnerabilityReports()) > 0 +func (r *ScanReport) HasFailedIssueReport() bool { + return len(r.GetFailedIssueReports()) > 0 } diff --git a/report/report_test.go b/report/report_test.go index 54d9f9cd..054bcbaa 100644 --- a/report/report_test.go +++ b/report/report_test.go @@ -7,12 +7,49 @@ import ( "testing" "time" + "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/internal/scan" "github.com/cerberauth/vulnapi/report" "github.com/stretchr/testify/assert" ) +func TestNewOperationSecurityScheme(t *testing.T) { + inHeader := auth.InHeader + tests := []struct { + name string + ss auth.SecurityScheme + want report.OperationSecurityScheme + }{ + { + name: "No Auth", + ss: auth.NewNoAuthSecurityScheme(), + want: report.OperationSecurityScheme{ + Type: auth.None, + Scheme: auth.NoneScheme, + In: nil, + }, + }, + { + name: "Bearer Token", + ss: auth.NewAuthorizationBearerSecurityScheme("test", nil), + want: report.OperationSecurityScheme{ + Type: auth.HttpType, + Scheme: auth.BearerScheme, + In: &inHeader, + Name: "test", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := report.NewOperationSecurityScheme(tt.ss) + assert.Equal(t, tt.want, got) + }) + } +} + func TestNewScanReport(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) @@ -75,60 +112,60 @@ func TestScanReport_HasData(t *testing.T) { func TestScanReport_AddScanAttempt(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - expectedScanAttempt := report.ReportScan{} + expectedScanAttempt := report.ScanReportScan{} - sr.AddScanAttempt(&scan.VulnerabilityScanAttempt{}) + sr.AddScanAttempt(&scan.IssueScanAttempt{}) assert.Equal(t, 1, len(sr.GetScanAttempts())) assert.Equal(t, expectedScanAttempt, sr.GetScanAttempts()[0]) } -func TestScanReport_AddVulnerabilityReport(t *testing.T) { +func TestScanReport_AddIssueReport(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - vulnerabilityReport := &report.VulnerabilityReport{} - sr.AddVulnerabilityReport(vulnerabilityReport) - assert.Equal(t, 1, len(sr.GetVulnerabilityReports())) - assert.Equal(t, vulnerabilityReport, sr.GetVulnerabilityReports()[0]) + IssueReport := &report.IssueReport{} + sr.AddIssueReport(IssueReport) + assert.Equal(t, 1, len(sr.GetIssueReports())) + assert.Equal(t, IssueReport, sr.GetIssueReports()[0]) } -func TestScanReport_HasFailedVulnerabilityReport(t *testing.T) { +func TestScanReport_HasFailedIssueReport(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - assert.False(t, sr.HasFailedVulnerabilityReport()) + assert.False(t, sr.HasFailedIssueReport()) issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) - assert.True(t, sr.HasFailedVulnerabilityReport()) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) + assert.True(t, sr.HasFailedIssueReport()) } -func TestScanReport_HasOnlyFailedVulnerabilityReport(t *testing.T) { +func TestScanReport_HasOnlyFailedIssueReport(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - assert.False(t, sr.HasFailedVulnerabilityReport()) + assert.False(t, sr.HasFailedIssueReport()) issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) - assert.True(t, sr.HasFailedVulnerabilityReport()) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) + assert.True(t, sr.HasFailedIssueReport()) } -func TestScanReport_HasOnlyPassedVulnerabilityReport(t *testing.T) { +func TestScanReport_HasOnlyPassedIssueReport(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - assert.False(t, sr.HasFailedVulnerabilityReport()) + assert.False(t, sr.HasFailedIssueReport()) issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Pass() - sr.AddVulnerabilityReport(vulnerabilityReport) - assert.False(t, sr.HasFailedVulnerabilityReport()) + IssueReport := report.NewIssueReport(issue).Pass() + sr.AddIssueReport(IssueReport) + assert.False(t, sr.HasFailedIssueReport()) } func TestScanReport_GetErrors(t *testing.T) { @@ -136,7 +173,7 @@ func TestScanReport_GetErrors(t *testing.T) { sr := report.NewScanReport("id", "test", operation) assert.Empty(t, sr.GetErrors()) - sr.AddScanAttempt(&scan.VulnerabilityScanAttempt{ + sr.AddScanAttempt(&scan.IssueScanAttempt{ Err: errors.New("test"), }) assert.NotEmpty(t, sr.GetErrors()) @@ -146,14 +183,30 @@ func TestScanReport_GetErrors(t *testing.T) { func TestMarshalJSON(t *testing.T) { operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) - scanAttempt := &scan.VulnerabilityScanAttempt{ + scanAttempt := &scan.IssueScanAttempt{ Err: nil, } sr.AddScanAttempt(scanAttempt) - vulnerabilityReport := &report.VulnerabilityReport{} - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := &report.IssueReport{} + sr.AddIssueReport(IssueReport) _, err := json.Marshal(sr) assert.NoError(t, err) } + +func TestScanReport_GetIssueReports(t *testing.T) { + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr := report.NewScanReport("id", "test", operation) + assert.Empty(t, sr.GetIssueReports()) + + issueReport1 := &report.IssueReport{} + issueReport2 := &report.IssueReport{} + sr.AddIssueReport(issueReport1) + sr.AddIssueReport(issueReport2) + + issueReports := sr.GetIssueReports() + assert.Equal(t, 2, len(issueReports)) + assert.Equal(t, issueReport1, issueReports[0]) + assert.Equal(t, issueReport2, issueReports[1]) +} diff --git a/report/reporter.go b/report/reporter.go index 58358b06..c3268174 100644 --- a/report/reporter.go +++ b/report/reporter.go @@ -1,25 +1,82 @@ package report +import ( + "net/http" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/getkin/kin-openapi/openapi3" +) + +const reporterSchema = "https://schemas.cerberauth.com/vulnapi/draft/2024-10/report.schema.json" + type Reporter struct { - Reports []*Report `json:"reports"` + Schema string `json:"$schema" yaml:"$schema"` + + Options OptionsReport `json:"options" yaml:"options"` + Curl *CurlReport `json:"curl,omitempty" yaml:"curl,omitempty"` + OpenAPI *OpenAPIReport `json:"openapi,omitempty" yaml:"openapi,omitempty"` + GraphQL *GraphQLReport `json:"graphql,omitempty" yaml:"graphql,omitempty"` + ScanReports []*ScanReport `json:"reports" yaml:"reports"` } func NewReporter() *Reporter { return &Reporter{ - Reports: []*Report{}, + Schema: reporterSchema, + + Options: NewOptionsReport(), + ScanReports: []*ScanReport{}, + } +} + +func NewReporterWithCurl(method string, url string, data interface{}, header http.Header, cookies []*http.Cookie, securitySchemes []auth.SecurityScheme) *Reporter { + return &Reporter{ + Schema: reporterSchema, + + Options: NewOptionsReport(), + Curl: NewCurlReport(method, url, data, header, cookies, securitySchemes), + ScanReports: []*ScanReport{}, + } +} + +func NewReporterWithOpenAPIDoc(openapi *openapi3.T, operations request.Operations) *Reporter { + return &Reporter{ + Schema: reporterSchema, + + Options: NewOptionsReport(), + OpenAPI: NewOpenAPIReport(openapi, operations), + ScanReports: []*ScanReport{}, } } -func (rr *Reporter) AddReport(r *Report) { - rr.Reports = append(rr.Reports, r) +func NewReporterWithGraphQL(url string, securitySchemes []auth.SecurityScheme) *Reporter { + return &Reporter{ + Schema: reporterSchema, + + Options: NewOptionsReport(), + GraphQL: NewGraphQLReport(url, securitySchemes), + ScanReports: []*ScanReport{}, + } +} + +func (rr *Reporter) AddReport(r *ScanReport) { + rr.ScanReports = append(rr.ScanReports, r) + + if rr.Curl != nil { + rr.Curl.AddReport(r) + } + + if rr.OpenAPI != nil { + rr.OpenAPI.AddReport(r) + } } -func (rr *Reporter) GetReports() []*Report { - return rr.Reports +func (rr *Reporter) GetScanReports() []*ScanReport { + return rr.ScanReports } -func (rr *Reporter) GetReportByID(id string) *Report { - for _, r := range rr.GetReports() { +func (rr *Reporter) GetScanReportByID(id string) *ScanReport { + for _, r := range rr.GetScanReports() { if r.ID == id { return r } @@ -28,11 +85,11 @@ func (rr *Reporter) GetReportByID(id string) *Report { return nil } -func (rr *Reporter) GetReportsByVulnerabilityStatus(status VulnerabilityReportStatus) []*Report { - var reports []*Report - for _, r := range rr.GetReports() { - for _, vr := range r.GetVulnerabilityReports() { - if vr.Status == status { +func (rr *Reporter) GetReportsByIssueStatus(status IssueReportStatus) []*ScanReport { + var reports []*ScanReport + for _, r := range rr.GetScanReports() { + for _, ir := range r.GetIssueReports() { + if ir.Status == status { reports = append(reports, r) break } @@ -44,16 +101,16 @@ func (rr *Reporter) GetReportsByVulnerabilityStatus(status VulnerabilityReportSt func (rr *Reporter) GetErrors() []error { var errors []error - for _, r := range rr.GetReports() { + for _, r := range rr.GetScanReports() { errors = append(errors, r.GetErrors()...) } return errors } -func (rr *Reporter) HasVulnerability() bool { - for _, r := range rr.GetReports() { - if r.HasFailedVulnerabilityReport() { +func (rr *Reporter) HasIssue() bool { + for _, r := range rr.GetScanReports() { + if r.HasFailedIssueReport() { return true } } @@ -61,26 +118,24 @@ func (rr *Reporter) HasVulnerability() bool { return false } -func (rr *Reporter) GetVulnerabilityReports() []*VulnerabilityReport { - var vrs []*VulnerabilityReport - for _, r := range rr.GetReports() { - vrs = append(vrs, r.GetVulnerabilityReports()...) +func (rr *Reporter) GetIssueReports() []*IssueReport { + var reports []*IssueReport + for _, r := range rr.GetScanReports() { + reports = append(reports, r.GetIssueReports()...) } - - return vrs + return reports } -func (rr *Reporter) GetFailedVulnerabilityReports() []*VulnerabilityReport { - var vrs []*VulnerabilityReport - for _, r := range rr.GetReports() { - vrs = append(vrs, r.GetFailedVulnerabilityReports()...) +func (rr *Reporter) GetFailedIssueReports() []*IssueReport { + var reports []*IssueReport + for _, r := range rr.GetScanReports() { + reports = append(reports, r.GetFailedIssueReports()...) } - - return vrs + return reports } -func (rr *Reporter) HasHighRiskOrHigherSeverityVulnerability() bool { - for _, r := range rr.GetFailedVulnerabilityReports() { +func (rr *Reporter) HasHighRiskOrHigherSeverityIssue() bool { + for _, r := range rr.GetFailedIssueReports() { if r.IsHighRiskSeverity() || r.IsCriticalRiskSeverity() { return true } @@ -89,8 +144,8 @@ func (rr *Reporter) HasHighRiskOrHigherSeverityVulnerability() bool { return false } -func (rr *Reporter) HasHigherThanSeverityThresholdVulnerability(threshold float64) bool { - for _, r := range rr.GetFailedVulnerabilityReports() { +func (rr *Reporter) HasHigherThanSeverityThresholdIssue(threshold float64) bool { + for _, r := range rr.GetFailedIssueReports() { if r.Issue.CVSS.Score >= threshold { return true } diff --git a/report/reporter_test.go b/report/reporter_test.go index d3931c9d..c17b8e20 100644 --- a/report/reporter_test.go +++ b/report/reporter_test.go @@ -1,17 +1,120 @@ package report_test import ( + "context" "net/http" "testing" + "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" + openapilib "github.com/cerberauth/vulnapi/openapi" "github.com/cerberauth/vulnapi/report" "github.com/stretchr/testify/assert" ) +func TestNewReporterWithCurl(t *testing.T) { + method := http.MethodPost + url := "http://localhost:8080/" + data := map[string]string{"key": "value"} + header := http.Header{"Content-Type": []string{"application/json"}} + cookies := []*http.Cookie{{Name: "session_id", Value: "abc123"}} + token := "abc123" + securityScheme := auth.SecurityScheme(auth.NewAuthorizationBearerSecurityScheme("token", &token)) + securitySchemes := []auth.SecurityScheme{securityScheme} + + reportSecuritySchemes := []report.OperationSecurityScheme{ + { + Type: securityScheme.GetType(), + Scheme: securityScheme.GetScheme(), + In: securityScheme.GetIn(), + Name: securityScheme.GetName(), + }, + } + + reporter := report.NewReporterWithCurl(method, url, data, header, cookies, securitySchemes) + + assert.NotNil(t, reporter) + assert.NotNil(t, reporter.Curl) + assert.Equal(t, method, reporter.Curl.Method) + assert.Equal(t, url, reporter.Curl.URL) + assert.Equal(t, data, reporter.Curl.Data) + assert.Equal(t, header, reporter.Curl.Header) + assert.Equal(t, cookies, reporter.Curl.Cookies) + assert.Equal(t, reportSecuritySchemes, reporter.Curl.SecuritySchemes) + assert.Empty(t, reporter.ScanReports) +} + +func TestNewReporterWithCurl_AddReport(t *testing.T) { + method := http.MethodPost + url := "http://localhost:8080/" + data := map[string]string{"key": "value"} + header := http.Header{"Content-Type": []string{"application/json"}} + cookies := []*http.Cookie{{Name: "session_id", Value: "abc123"}} + token := "abc123" + securityScheme := auth.SecurityScheme(auth.NewAuthorizationBearerSecurityScheme("token", &token)) + securitySchemes := []auth.SecurityScheme{securityScheme} + + reporter := report.NewReporterWithCurl(method, url, data, header, cookies, securitySchemes) + + issue := report.Issue{ + ID: "id", + Name: "test", + } + sr := report.NewScanReport("id", "test", nil).AddIssueReport(report.NewIssueReport(issue).Fail()) + reporter.AddReport(sr) + expectedIssue := &report.IssueReport{ + Issue: issue, + Status: report.IssueReportStatusFailed, + } + + assert.NotEmpty(t, reporter.ScanReports) + assert.Equal(t, 1, len(reporter.ScanReports)) + assert.Equal(t, sr, reporter.ScanReports[0]) + assert.NotEmpty(t, reporter.Curl.Issues) + assert.Equal(t, 1, len(reporter.Curl.Issues)) + assert.Equal(t, expectedIssue, reporter.Curl.Issues[0]) +} + +func TestNewReporterWithOpenAPIDoc(t *testing.T) { + openapi, _ := openapilib.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := openapi.SecuritySchemeMap(auth.NewEmptySecuritySchemeValues()) + operations, _ := openapi.Operations(nil, securitySchemesMap) + + reporter := report.NewReporterWithOpenAPIDoc(openapi.Doc, operations) + + assert.NotNil(t, reporter) + assert.NotNil(t, reporter.OpenAPI) + assert.Empty(t, reporter.ScanReports) +} + +func TestReporterWithOpenAPIDoc_AddReport(t *testing.T) { + openapi, _ := openapilib.LoadOpenAPI(context.Background(), "../test/stub/simple_http_bearer.openapi.json") + securitySchemesMap, _ := openapi.SecuritySchemeMap(auth.NewEmptySecuritySchemeValues()) + operations, _ := openapi.Operations(nil, securitySchemesMap) + reporter := report.NewReporterWithOpenAPIDoc(openapi.Doc, operations) + + issue := report.Issue{ + ID: "id", + Name: "test", + } + sr := report.NewScanReport("id", "test", operations[0]).AddIssueReport(report.NewIssueReport(issue).Fail()) + reporter.AddReport(sr) + expectedIssue := &report.IssueReport{ + Issue: issue, + Status: report.IssueReportStatusFailed, + } + + assert.NotEmpty(t, reporter.ScanReports) + assert.Equal(t, 1, len(reporter.ScanReports)) + assert.Equal(t, sr, reporter.ScanReports[0]) + assert.NotEmpty(t, reporter.OpenAPI.Paths["/"][http.MethodGet].Issues) + assert.Equal(t, 1, len(reporter.OpenAPI.Paths["/"][http.MethodGet].Issues)) + assert.Equal(t, expectedIssue, reporter.OpenAPI.Paths["/"][http.MethodGet].Issues[0]) +} + func TestReporter_NoHasHighRiskOrHigherSeverityVulnerability_WhenNoReport(t *testing.T) { reporter := report.NewReporter() - assert.False(t, reporter.HasHighRiskOrHigherSeverityVulnerability()) + assert.False(t, reporter.HasHighRiskOrHigherSeverityIssue()) } func TestReporter_NoHasVulnerability_WhenNoFailedReport(t *testing.T) { @@ -21,11 +124,11 @@ func TestReporter_NoHasVulnerability_WhenNoFailedReport(t *testing.T) { issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Pass() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Pass() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.False(t, reporter.HasVulnerability()) + assert.False(t, reporter.HasIssue()) } func TestReporter_HasVulnerability_WhenFailedReport(t *testing.T) { @@ -35,11 +138,11 @@ func TestReporter_HasVulnerability_WhenFailedReport(t *testing.T) { issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.True(t, reporter.HasVulnerability()) + assert.True(t, reporter.HasIssue()) } func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenLowRiskReport(t *testing.T) { @@ -52,11 +155,11 @@ func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenLowRiskReport(t Score: 0.1, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.False(t, reporter.HasHighRiskOrHigherSeverityVulnerability()) + assert.False(t, reporter.HasHighRiskOrHigherSeverityIssue()) } func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenHighRiskReport(t *testing.T) { @@ -69,11 +172,11 @@ func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenHighRiskReport(t Score: 8, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.True(t, reporter.HasHighRiskOrHigherSeverityVulnerability()) + assert.True(t, reporter.HasHighRiskOrHigherSeverityIssue()) } func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenCriticalRiskReport(t *testing.T) { @@ -86,19 +189,19 @@ func TestReporters_HasHighRiskOrHigherSeverityVulnerability_WhenCriticalRiskRepo Score: 9.8, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.True(t, reporter.HasHighRiskOrHigherSeverityVulnerability()) + assert.True(t, reporter.HasHighRiskOrHigherSeverityIssue()) } -func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenNoReports(t *testing.T) { +func TestReporter_HasHigherThanSeverityThresholdIssue_WhenNoReports(t *testing.T) { reporter := report.NewReporter() - assert.False(t, reporter.HasHigherThanSeverityThresholdVulnerability(5.0)) + assert.False(t, reporter.HasHigherThanSeverityThresholdIssue(5.0)) } -func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenBelowThreshold(t *testing.T) { +func TestReporter_HasHigherThanSeverityThresholdIssue_WhenBelowThreshold(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) @@ -108,14 +211,14 @@ func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenBelowThreshold Score: 4.0, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.False(t, reporter.HasHigherThanSeverityThresholdVulnerability(5.0)) + assert.False(t, reporter.HasHigherThanSeverityThresholdIssue(5.0)) } -func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenAtThreshold(t *testing.T) { +func TestReporter_HasHigherThanSeverityThresholdIssue_WhenAtThreshold(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) @@ -125,14 +228,14 @@ func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenAtThreshold(t Score: 5.0, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.True(t, reporter.HasHigherThanSeverityThresholdVulnerability(5.0)) + assert.True(t, reporter.HasHigherThanSeverityThresholdIssue(5.0)) } -func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenAboveThreshold(t *testing.T) { +func TestReporter_HasHigherThanSeverityThresholdIssue_WhenAboveThreshold(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) @@ -142,73 +245,166 @@ func TestReporter_HasHigherThanSeverityThresholdVulnerability_WhenAboveThreshold Score: 7.0, }, } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - assert.True(t, reporter.HasHigherThanSeverityThresholdVulnerability(5.0)) + assert.True(t, reporter.HasHigherThanSeverityThresholdIssue(5.0)) } -func TestReporter_GetReportsByVulnerabilityStatus_NoReports(t *testing.T) { +func TestReporter_GetReportsByIssueStatus_NoReports(t *testing.T) { reporter := report.NewReporter() - reports := reporter.GetReportsByVulnerabilityStatus(report.VulnerabilityReportStatusFailed) + reports := reporter.GetReportsByIssueStatus(report.IssueReportStatusFailed) assert.Empty(t, reports) } -func TestReporter_GetReportsByVulnerabilityStatus_NoMatchingStatus(t *testing.T) { +func TestReporter_GetReportsByIssueStatus_NoMatchingStatus(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Pass() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Pass() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - reports := reporter.GetReportsByVulnerabilityStatus(report.VulnerabilityReportStatusFailed) + reports := reporter.GetReportsByIssueStatus(report.IssueReportStatusFailed) assert.Empty(t, reports) } -func TestReporter_GetReportsByVulnerabilityStatus_MatchingStatus(t *testing.T) { +func TestReporter_GetReportsByIssueStatus_MatchingStatus(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr := report.NewScanReport("id", "test", operation) issue := report.Issue{ Name: "test", } - vulnerabilityReport := report.NewVulnerabilityReport(issue).Fail() - sr.AddVulnerabilityReport(vulnerabilityReport) + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) reporter.AddReport(sr) - reports := reporter.GetReportsByVulnerabilityStatus(report.VulnerabilityReportStatusFailed) + reports := reporter.GetReportsByIssueStatus(report.IssueReportStatusFailed) assert.NotEmpty(t, reports) assert.Equal(t, 1, len(reports)) assert.Equal(t, "id", reports[0].ID) } -func TestReporter_GetReportsByVulnerabilityStatus_MultipleReports(t *testing.T) { +func TestReporter_GetReportsByIssueStatus_MultipleReports(t *testing.T) { reporter := report.NewReporter() operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) sr1 := report.NewScanReport("id1", "test1", operation) issue1 := report.Issue{ Name: "test1", } - vulnerabilityReport1 := report.NewVulnerabilityReport(issue1).Fail() - sr1.AddVulnerabilityReport(vulnerabilityReport1) + IssueReport1 := report.NewIssueReport(issue1).Fail() + sr1.AddIssueReport(IssueReport1) reporter.AddReport(sr1) sr2 := report.NewScanReport("id2", "test2", operation) issue2 := report.Issue{ Name: "test2", } - vulnerabilityReport2 := report.NewVulnerabilityReport(issue2).Fail() - sr2.AddVulnerabilityReport(vulnerabilityReport2) + IssueReport2 := report.NewIssueReport(issue2).Fail() + sr2.AddIssueReport(IssueReport2) reporter.AddReport(sr2) - reports := reporter.GetReportsByVulnerabilityStatus(report.VulnerabilityReportStatusFailed) + reports := reporter.GetReportsByIssueStatus(report.IssueReportStatusFailed) assert.NotEmpty(t, reports) assert.Equal(t, 2, len(reports)) assert.Equal(t, "id1", reports[0].ID) assert.Equal(t, "id2", reports[1].ID) } + +func TestReporter_GetIssueReports_NoReports(t *testing.T) { + reporter := report.NewReporter() + reports := reporter.GetIssueReports() + assert.Empty(t, reports) +} + +func TestReporter_GetIssueReports_SingleReport(t *testing.T) { + reporter := report.NewReporter() + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr := report.NewScanReport("id", "test", operation) + issue := report.Issue{ + Name: "test", + } + IssueReport := report.NewIssueReport(issue).Fail() + sr.AddIssueReport(IssueReport) + reporter.AddReport(sr) + + reports := reporter.GetIssueReports() + assert.NotEmpty(t, reports) + assert.Equal(t, 1, len(reports)) + assert.Equal(t, "test", reports[0].Issue.Name) +} + +func TestReporter_GetIssueReports_MultipleReports(t *testing.T) { + reporter := report.NewReporter() + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr1 := report.NewScanReport("id1", "test1", operation) + issue1 := report.Issue{ + Name: "test1", + } + IssueReport1 := report.NewIssueReport(issue1).Fail() + sr1.AddIssueReport(IssueReport1) + reporter.AddReport(sr1) + + sr2 := report.NewScanReport("id2", "test2", operation) + issue2 := report.Issue{ + Name: "test2", + } + IssueReport2 := report.NewIssueReport(issue2).Fail() + sr2.AddIssueReport(IssueReport2) + reporter.AddReport(sr2) + + reports := reporter.GetIssueReports() + assert.NotEmpty(t, reports) + assert.Equal(t, 2, len(reports)) + assert.Equal(t, "test1", reports[0].Issue.Name) + assert.Equal(t, "test2", reports[1].Issue.Name) +} + +func TestReporter_GetScanReportByID_NoReports(t *testing.T) { + reporter := report.NewReporter() + report := reporter.GetScanReportByID("nonexistent_id") + assert.Nil(t, report) +} + +func TestReporter_GetScanReportByID_SingleReport(t *testing.T) { + reporter := report.NewReporter() + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr := report.NewScanReport("id", "test", operation) + reporter.AddReport(sr) + + report := reporter.GetScanReportByID("id") + assert.NotNil(t, report) + assert.Equal(t, "id", report.ID) +} + +func TestReporter_GetScanReportByID_MultipleReports(t *testing.T) { + reporter := report.NewReporter() + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr1 := report.NewScanReport("id1", "test1", operation) + sr2 := report.NewScanReport("id2", "test2", operation) + reporter.AddReport(sr1) + reporter.AddReport(sr2) + + report1 := reporter.GetScanReportByID("id1") + report2 := reporter.GetScanReportByID("id2") + + assert.NotNil(t, report1) + assert.Equal(t, "id1", report1.ID) + assert.NotNil(t, report2) + assert.Equal(t, "id2", report2.ID) +} + +func TestReporter_GetScanReportByID_NonexistentID(t *testing.T) { + reporter := report.NewReporter() + operation, _ := request.NewOperation(http.MethodPost, "http://localhost:8080/", nil, nil) + sr := report.NewScanReport("id", "test", operation) + reporter.AddReport(sr) + + report := reporter.GetScanReportByID("nonexistent_id") + assert.Nil(t, report) +} diff --git a/report/vuln.go b/report/vuln.go deleted file mode 100644 index 7d8368e7..00000000 --- a/report/vuln.go +++ /dev/null @@ -1,138 +0,0 @@ -package report - -import ( - "fmt" - - "github.com/cerberauth/vulnapi/internal/auth" - "github.com/cerberauth/vulnapi/internal/request" -) - -type VulnerabilityReportStatus string - -func (vrs VulnerabilityReportStatus) String() string { - return string(vrs) -} - -const ( - VulnerabilityReportStatusPassed VulnerabilityReportStatus = "passed" - VulnerabilityReportStatusFailed VulnerabilityReportStatus = "failed" - VulnerabilityReportStatusSkipped VulnerabilityReportStatus = "skipped" - VulnerabilityReportStatusNone VulnerabilityReportStatus = "none" -) - -var VulnerabilityReportStatuses = []VulnerabilityReportStatus{ - VulnerabilityReportStatusPassed, - VulnerabilityReportStatusFailed, - VulnerabilityReportStatusSkipped, - VulnerabilityReportStatusNone, -} - -type VulnerabilityReport struct { - Issue `json:",inline" yaml:",inline"` - Status VulnerabilityReportStatus `json:"status" yaml:"status"` - - Operation *request.Operation `json:"-" yaml:"-"` - SecurityScheme auth.SecurityScheme `json:"-" yaml:"-"` -} - -func NewVulnerabilityReport(issue Issue) *VulnerabilityReport { - return &VulnerabilityReport{ - Issue: issue, - - Status: VulnerabilityReportStatusNone, - } -} - -func (vr *VulnerabilityReport) WithOperation(operation *request.Operation) *VulnerabilityReport { - vr.Operation = operation - return vr -} - -func (vr *VulnerabilityReport) WithSecurityScheme(ss auth.SecurityScheme) *VulnerabilityReport { - vr.SecurityScheme = ss - return vr -} - -func (vr *VulnerabilityReport) WithStatus(status VulnerabilityReportStatus) *VulnerabilityReport { - vr.Status = status - return vr -} - -func (vr *VulnerabilityReport) WithBooleanStatus(status bool) *VulnerabilityReport { - if status { - return vr.Pass() - } - return vr.Fail() -} - -func (vr *VulnerabilityReport) Fail() *VulnerabilityReport { - vr.Status = VulnerabilityReportStatusFailed - return vr -} - -func (vr *VulnerabilityReport) HasFailed() bool { - return vr.Status == VulnerabilityReportStatusFailed -} - -func (vr *VulnerabilityReport) Pass() *VulnerabilityReport { - vr.Status = VulnerabilityReportStatusPassed - return vr -} - -func (vr *VulnerabilityReport) HasPassed() bool { - return vr.Status == VulnerabilityReportStatusPassed -} - -func (vr *VulnerabilityReport) Skip() *VulnerabilityReport { - vr.Status = VulnerabilityReportStatusSkipped - return vr -} - -func (vr *VulnerabilityReport) HasBeenSkipped() bool { - return vr.Status == VulnerabilityReportStatusSkipped -} - -func (vr *VulnerabilityReport) IsInfoRiskSeverity() bool { - return vr.CVSS.Score == 0 -} - -func (vr *VulnerabilityReport) IsLowRiskSeverity() bool { - return vr.CVSS.Score < 4 && vr.CVSS.Score > 0 -} - -func (vr *VulnerabilityReport) IsMediumRiskSeverity() bool { - return vr.CVSS.Score > 4 && vr.CVSS.Score < 7 -} - -func (vr *VulnerabilityReport) IsHighRiskSeverity() bool { - return vr.CVSS.Score > 7 && vr.CVSS.Score < 9 -} - -func (vr *VulnerabilityReport) IsCriticalRiskSeverity() bool { - return vr.CVSS.Score > 9 -} - -func (vr *VulnerabilityReport) String() string { - return fmt.Sprintf("[%s] %s", vr.SeverityLevelString(), vr.Name) -} - -func (vr *VulnerabilityReport) SeverityLevelString() string { - switch { - case vr.IsCriticalRiskSeverity(): - return "Critical" - case vr.IsHighRiskSeverity(): - return "High" - case vr.IsMediumRiskSeverity(): - return "Medium" - case vr.IsLowRiskSeverity(): - return "Low" - case vr.IsInfoRiskSeverity(): - return "Info" - default: - return "None" - } -} - -func (vr *VulnerabilityReport) Clone() *VulnerabilityReport { - return NewVulnerabilityReport(vr.Issue).WithOperation(vr.Operation).WithSecurityScheme(vr.SecurityScheme).WithStatus(vr.Status) -} diff --git a/scan/broken_authentication/authentication_bypass/authentication_bypass.go b/scan/broken_authentication/authentication_bypass/authentication_bypass.go index 50080382..3d2d2db2 100644 --- a/scan/broken_authentication/authentication_bypass/authentication_bypass.go +++ b/scan/broken_authentication/authentication_bypass/authentication_bypass.go @@ -27,12 +27,12 @@ var issue = report.Issue{ }, } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(AcceptsUnauthenticatedOperationScanID, AcceptsUnauthenticatedOperationScanName, operation) if _, ok := securityScheme.(*auth.NoAuthSecurityScheme); ok { - return r.AddVulnerabilityReport(vulnReport.Skip()).End(), nil + return r.AddIssueReport(vulnReport.Skip()).End(), nil } noAuthSecurityScheme := auth.SecurityScheme(auth.NewNoAuthSecurityScheme()) @@ -41,7 +41,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem return r, err } vulnReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) - r.AddVulnerabilityReport(vulnReport).AddScanAttempt(vsa).End() + r.AddIssueReport(vulnReport).AddScanAttempt(vsa).End() return r, nil } diff --git a/scan/broken_authentication/authentication_bypass/authentication_bypass_test.go b/scan/broken_authentication/authentication_bypass/authentication_bypass_test.go index bd3cb0b0..51f5baa2 100644 --- a/scan/broken_authentication/authentication_bypass/authentication_bypass_test.go +++ b/scan/broken_authentication/authentication_bypass/authentication_bypass_test.go @@ -19,7 +19,7 @@ func TestAuthenticationByPassScanHandler_Skipped_WhenNoAuthSecurityScheme(t *tes report, err := authenticationbypass.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestAuthenticationByPassScanHandler_Failed_WhenAuthIsByPassed(t *testing.T) { @@ -35,7 +35,7 @@ func TestAuthenticationByPassScanHandler_Failed_WhenAuthIsByPassed(t *testing.T) report, err := authenticationbypass.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } func TestAuthenticationByPassScanHandler_Passed_WhenAuthIsNotByPassed(t *testing.T) { @@ -51,5 +51,5 @@ func TestAuthenticationByPassScanHandler_Passed_WhenAuthIsNotByPassed(t *testing report, err := authenticationbypass.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } diff --git a/scan/broken_authentication/jwt/alg_none/alg_none.go b/scan/broken_authentication/jwt/alg_none/alg_none.go index 36df6a12..cb69d742 100644 --- a/scan/broken_authentication/jwt/alg_none/alg_none.go +++ b/scan/broken_authentication/jwt/alg_none/alg_none.go @@ -43,13 +43,13 @@ func ShouldBeScanned(securitySheme auth.SecurityScheme) bool { return true } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(AlgNoneJwtScanID, AlgNoneJwtScanName, operation) if !ShouldBeScanned(securityScheme) { vulnReport.Skip() - r.AddVulnerabilityReport(vulnReport).End() + r.AddIssueReport(vulnReport).End() return r, nil } @@ -74,7 +74,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem } r.AddScanAttempt(vsa).End() vulnReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) - r.AddVulnerabilityReport(vulnReport) + r.AddIssueReport(vulnReport) return r, nil } diff --git a/scan/broken_authentication/jwt/alg_none/alg_none_test.go b/scan/broken_authentication/jwt/alg_none/alg_none_test.go index 2d5f5a8b..f94f89df 100644 --- a/scan/broken_authentication/jwt/alg_none/alg_none_test.go +++ b/scan/broken_authentication/jwt/alg_none/alg_none_test.go @@ -19,7 +19,7 @@ func TestAlgNoneJwtScanHandler_WithoutSecurityScheme(t *testing.T) { report, err := algnone.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestAlgNoneJwtScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testing.T) { @@ -34,7 +34,7 @@ func TestAlgNoneJwtScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testin report, err := algnone.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestAlgNoneJwtScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) { @@ -50,7 +50,7 @@ func TestAlgNoneJwtScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) { report, err := algnone.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestAlgNoneJwtScanHandler_Failed_WhenOKResponse(t *testing.T) { @@ -66,5 +66,5 @@ func TestAlgNoneJwtScanHandler_Failed_WhenOKResponse(t *testing.T) { report, err := algnone.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/broken_authentication/jwt/blank_secret/blank_secret.go b/scan/broken_authentication/jwt/blank_secret/blank_secret.go index a057312f..7e631dc7 100644 --- a/scan/broken_authentication/jwt/blank_secret/blank_secret.go +++ b/scan/broken_authentication/jwt/blank_secret/blank_secret.go @@ -42,12 +42,12 @@ func ShouldBeScanned(securitySheme auth.SecurityScheme) bool { return true } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(BlankSecretVulnerabilityScanID, BlankSecretVulnerabilityScanName, operation) if !ShouldBeScanned(securityScheme) { - return r.AddVulnerabilityReport(vulnReport.Skip()).End(), nil + return r.AddIssueReport(vulnReport.Skip()).End(), nil } var valueWriter *jwt.JWTWriter @@ -67,7 +67,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem return r, err } vulnReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) - r.AddScanAttempt(vsa).AddVulnerabilityReport(vulnReport).End() + r.AddScanAttempt(vsa).AddIssueReport(vulnReport).End() return r, nil } diff --git a/scan/broken_authentication/jwt/blank_secret/blank_secret_test.go b/scan/broken_authentication/jwt/blank_secret/blank_secret_test.go index 6f6a74cc..624ed5a3 100644 --- a/scan/broken_authentication/jwt/blank_secret/blank_secret_test.go +++ b/scan/broken_authentication/jwt/blank_secret/blank_secret_test.go @@ -19,7 +19,7 @@ func TestBlankSecretScanHandler_WithoutSecurityScheme(t *testing.T) { report, err := blanksecret.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestBlankSecretScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testing.T) { @@ -35,7 +35,7 @@ func TestBlankSecretScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testi require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestBlankSecretScanHandler_Passed_WhenNoJWTAndOKResponse(t *testing.T) { @@ -51,7 +51,7 @@ func TestBlankSecretScanHandler_Passed_WhenNoJWTAndOKResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } func TestBlankSecretScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) { @@ -68,7 +68,7 @@ func TestBlankSecretScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestBlankSecretScanHandler_Failed_WhenOKResponse(t *testing.T) { @@ -85,5 +85,5 @@ func TestBlankSecretScanHandler_Failed_WhenOKResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/broken_authentication/jwt/not_verified/not_verified.go b/scan/broken_authentication/jwt/not_verified/not_verified.go index 60566b29..3324e35b 100644 --- a/scan/broken_authentication/jwt/not_verified/not_verified.go +++ b/scan/broken_authentication/jwt/not_verified/not_verified.go @@ -45,12 +45,12 @@ func ShouldBeScanned(securitySheme auth.SecurityScheme) bool { return true } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(NotVerifiedJwtScanID, NotVerifiedJwtScanName, operation) if !ShouldBeScanned(securityScheme) { - r.AddVulnerabilityReport(vulnReport.Skip()).End() + r.AddIssueReport(vulnReport.Skip()).End() return r, nil } @@ -69,7 +69,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem r.AddScanAttempt(attemptOne).End() if !scan.IsUnauthorizedStatusCodeOrSimilar(attemptOne.Response) { - r.AddVulnerabilityReport(vulnReport.Skip()) + r.AddIssueReport(vulnReport.Skip()) return r, nil } @@ -81,7 +81,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem r.AddScanAttempt(attemptTwo).End() vulnReport.WithBooleanStatus(attemptOne.Response.StatusCode == attemptTwo.Response.StatusCode) - r.AddVulnerabilityReport(vulnReport) + r.AddIssueReport(vulnReport) return r, nil } diff --git a/scan/broken_authentication/jwt/not_verified/not_verified_test.go b/scan/broken_authentication/jwt/not_verified/not_verified_test.go index de1d8cb9..c8367000 100644 --- a/scan/broken_authentication/jwt/not_verified/not_verified_test.go +++ b/scan/broken_authentication/jwt/not_verified/not_verified_test.go @@ -19,7 +19,7 @@ func TestNotVerifiedScanHandler_WithoutSecurityScheme(t *testing.T) { report, err := notverified.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestNotVerifiedScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testing.T) { @@ -29,7 +29,7 @@ func TestNotVerifiedScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testi report, err := notverified.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestNotVerifiedScanHandler_Failed_WhenUnauthorizedThenOK(t *testing.T) { @@ -51,7 +51,7 @@ func TestNotVerifiedScanHandler_Failed_WhenUnauthorizedThenOK(t *testing.T) { require.NoError(t, err) assert.Equal(t, 2, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } func TestNotVerifiedScanHandler_Skipped_WhenOKFirstRequest(t *testing.T) { @@ -73,7 +73,7 @@ func TestNotVerifiedScanHandler_Skipped_WhenOKFirstRequest(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestNotVerifiedScanHandler_Failed_WhenUnauthorizedThenUnauthorized(t *testing.T) { @@ -95,5 +95,5 @@ func TestNotVerifiedScanHandler_Failed_WhenUnauthorizedThenUnauthorized(t *testi require.NoError(t, err) assert.Equal(t, 2, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } diff --git a/scan/broken_authentication/jwt/null_signature/null_signature.go b/scan/broken_authentication/jwt/null_signature/null_signature.go index 00984c2e..0e024427 100644 --- a/scan/broken_authentication/jwt/null_signature/null_signature.go +++ b/scan/broken_authentication/jwt/null_signature/null_signature.go @@ -42,12 +42,12 @@ func ShouldBeScanned(securitySheme auth.SecurityScheme) bool { return true } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(NullSignatureScanID, NullSignatureScanName, operation) if !ShouldBeScanned(securityScheme) { - r.AddVulnerabilityReport(vulnReport.Skip()).End() + r.AddIssueReport(vulnReport.Skip()).End() return r, nil } @@ -69,7 +69,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem } r.AddScanAttempt(vsa).End() vulnReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) - r.AddVulnerabilityReport(vulnReport) + r.AddIssueReport(vulnReport) return r, nil } diff --git a/scan/broken_authentication/jwt/null_signature/null_signature_test.go b/scan/broken_authentication/jwt/null_signature/null_signature_test.go index d8cbb3e8..d6900d16 100644 --- a/scan/broken_authentication/jwt/null_signature/null_signature_test.go +++ b/scan/broken_authentication/jwt/null_signature/null_signature_test.go @@ -19,7 +19,7 @@ func TestNullSignatureScanHandler_WithoutSecurityScheme(t *testing.T) { report, err := nullsignature.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestNullSignatureScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *testing.T) { @@ -35,7 +35,7 @@ func TestNullSignatureScanHandler_Passed_WhenNoJWTAndUnauthorizedResponse(t *tes require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestNullSignatureScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) { @@ -52,7 +52,7 @@ func TestNullSignatureScanHandler_Passed_WhenUnauthorizedResponse(t *testing.T) require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestNullSignatureScanHandler_Failed_WhenOKResponse(t *testing.T) { @@ -69,5 +69,5 @@ func TestNullSignatureScanHandler_Failed_WhenOKResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/broken_authentication/jwt/weak_secret/weak_secret.go b/scan/broken_authentication/jwt/weak_secret/weak_secret.go index e075c761..75b3031c 100644 --- a/scan/broken_authentication/jwt/weak_secret/weak_secret.go +++ b/scan/broken_authentication/jwt/weak_secret/weak_secret.go @@ -56,12 +56,12 @@ var defaultJwtSecretDictionary = []string{"secret", "password", "123456", "chang const jwtSecretDictionarySeclistUrl = "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/scraped-JWT-secrets.txt" -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(WeakSecretVulnerabilityScanID, WeakSecretVulnerabilityScanName, operation) if !ShouldBeScanned(securityScheme) { - r.AddVulnerabilityReport(vulnReport.Skip()).End() + r.AddIssueReport(vulnReport.Skip()).End() return r, nil } @@ -102,7 +102,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem break } - r.AddVulnerabilityReport(vulnReport.WithBooleanStatus(!secretFound)).End() + r.AddIssueReport(vulnReport.WithBooleanStatus(!secretFound)).End() return r, nil } diff --git a/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go b/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go index c83b6824..d69dc61c 100644 --- a/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go +++ b/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go @@ -19,7 +19,7 @@ func TestWeakHMACSecretScanHandler_WithoutSecurityScheme(t *testing.T) { report, err := weaksecret.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestWeakHMACSecretScanHandler_WithJWTUsingOtherAlg(t *testing.T) { @@ -30,7 +30,7 @@ func TestWeakHMACSecretScanHandler_WithJWTUsingOtherAlg(t *testing.T) { report, err := weaksecret.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestWeakHMACSecretScanHandler_WithoutJWT(t *testing.T) { @@ -40,7 +40,7 @@ func TestWeakHMACSecretScanHandler_WithoutJWT(t *testing.T) { report, err := weaksecret.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasBeenSkipped()) + assert.True(t, report.Issues[0].HasBeenSkipped()) } func TestWeakHMACSecretScanHandler_Failed_WithWeakJWT(t *testing.T) { @@ -58,7 +58,7 @@ func TestWeakHMACSecretScanHandler_Failed_WithWeakJWT(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, &secret, report.Data.(*weaksecret.WeakSecretData).Secret) } @@ -72,6 +72,6 @@ func TestWeakHMACSecretScanHandler_Passed_WithStrongerJWT(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) assert.Nil(t, report.Data, nil) } diff --git a/scan/discover/accept_unauthenticated/accept_unauthenticated_operation.go b/scan/discover/accept_unauthenticated/accept_unauthenticated_operation.go index e4be7255..defff7c5 100644 --- a/scan/discover/accept_unauthenticated/accept_unauthenticated_operation.go +++ b/scan/discover/accept_unauthenticated/accept_unauthenticated_operation.go @@ -26,12 +26,12 @@ var issue = report.Issue{ }, } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(NoAuthOperationScanID, NoAuthOperationScanName, operation) _, ok := securityScheme.(*auth.NoAuthSecurityScheme) - r.AddVulnerabilityReport(vulnReport.WithBooleanStatus(!ok)).End() + r.AddIssueReport(vulnReport.WithBooleanStatus(!ok)).End() r.End() return r, nil diff --git a/scan/discover/accept_unauthenticated/accept_unauthenticated_operation_test.go b/scan/discover/accept_unauthenticated/accept_unauthenticated_operation_test.go index c8aeddf7..b2b5bc57 100644 --- a/scan/discover/accept_unauthenticated/accept_unauthenticated_operation_test.go +++ b/scan/discover/accept_unauthenticated/accept_unauthenticated_operation_test.go @@ -18,7 +18,7 @@ func TestAcceptUnauthenticatedScanHandler_Failed_WhenNoAuthSecurityScheme(t *tes report, err := acceptunauthenticated.ScanHandler(operation, securityScheme) assert.NoError(t, err) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } func TestCheckNoAuthOperationScanHandler_Passed_WhenAuthConfigured(t *testing.T) { @@ -29,5 +29,5 @@ func TestCheckNoAuthOperationScanHandler_Passed_WhenAuthConfigured(t *testing.T) report, err := acceptunauthenticated.ScanHandler(operation, securityScheme) require.NoError(t, err) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } diff --git a/scan/discover/discoverable_graphql/discoverable_graphql.go b/scan/discover/discoverable_graphql/discoverable_graphql.go index 0c17400b..19686056 100644 --- a/scan/discover/discoverable_graphql/discoverable_graphql.go +++ b/scan/discover/discoverable_graphql/discoverable_graphql.go @@ -39,8 +39,8 @@ var potentialGraphQLEndpoints = []string{ "/v1/graphiql", } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(DiscoverableGraphQLPathScanID, DiscoverableGraphQLPathScanName, operation) handler := discover.CreateURLScanHandler("GraphQL", graphqlSeclistUrl, potentialGraphQLEndpoints, r, vulnReport) diff --git a/scan/discover/discoverable_graphql/discoverable_graphql_test.go b/scan/discover/discoverable_graphql/discoverable_graphql_test.go index c1542288..99b99a87 100644 --- a/scan/discover/discoverable_graphql/discoverable_graphql_test.go +++ b/scan/discover/discoverable_graphql/discoverable_graphql_test.go @@ -27,7 +27,7 @@ func TestDiscoverableScanner_Passed_WhenNoDiscoverableGraphqlPathFound(t *testin require.NoError(t, err) assert.Greater(t, httpmock.GetTotalCallCount(), 7) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestDiscoverableScanner_Failed_WhenOneGraphQLPathFound(t *testing.T) { @@ -45,5 +45,5 @@ func TestDiscoverableScanner_Failed_WhenOneGraphQLPathFound(t *testing.T) { require.NoError(t, err) assert.Greater(t, httpmock.GetTotalCallCount(), 0) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/discover/discoverable_openapi/discoverable_openapi.go b/scan/discover/discoverable_openapi/discoverable_openapi.go index ed945f01..e6638532 100644 --- a/scan/discover/discoverable_openapi/discoverable_openapi.go +++ b/scan/discover/discoverable_openapi/discoverable_openapi.go @@ -38,8 +38,8 @@ var potentialOpenAPIPaths = []string{ "/.well-known/openapi.yml", } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(DiscoverableOpenAPIScanID, DiscoverableOpenAPIScanName, operation) handler := discover.CreateURLScanHandler("OpenAPI", openapiSeclistUrl, potentialOpenAPIPaths, r, vulnReport) diff --git a/scan/discover/discoverable_openapi/discoverable_openapi_test.go b/scan/discover/discoverable_openapi/discoverable_openapi_test.go index 09c08a00..12c4c934 100644 --- a/scan/discover/discoverable_openapi/discoverable_openapi_test.go +++ b/scan/discover/discoverable_openapi/discoverable_openapi_test.go @@ -27,7 +27,7 @@ func TestDiscoverableScanner_Passed_WhenNoDiscoverableGraphqlPathFound(t *testin require.NoError(t, err) assert.Greater(t, httpmock.GetTotalCallCount(), 10) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestDiscoverableScanner_Failed_WhenOneOpenAPIFound(t *testing.T) { @@ -45,5 +45,5 @@ func TestDiscoverableScanner_Failed_WhenOneOpenAPIFound(t *testing.T) { require.NoError(t, err) assert.Greater(t, httpmock.GetTotalCallCount(), 0) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/discover/fingerprint/fingerprint.go b/scan/discover/fingerprint/fingerprint.go index c3fc3938..699fe613 100644 --- a/scan/discover/fingerprint/fingerprint.go +++ b/scan/discover/fingerprint/fingerprint.go @@ -16,24 +16,24 @@ const ( ) type FingerPrintApp struct { - Name string `json:"name"` + Name string `json:"name" yaml:"name"` Version *string `json:"version,omitempty"` } type FingerPrintData struct { - CertificateAuthority []FingerPrintApp `json:"certificate_authority"` - Hosting []FingerPrintApp `json:"hosting"` - OS []FingerPrintApp `json:"os"` - Softwares []FingerPrintApp `json:"softwares"` - Databases []FingerPrintApp `json:"databases"` - Servers []FingerPrintApp `json:"servers"` - ServerExtensions []FingerPrintApp `json:"server_extensions"` - AuthServices []FingerPrintApp `json:"auth_services"` - CDNs []FingerPrintApp `json:"cdns"` - Caching []FingerPrintApp `json:"cache"` - Languages []FingerPrintApp `json:"languages"` - Frameworks []FingerPrintApp `json:"frameworks"` - SecurityServices []FingerPrintApp `json:"security_services"` + CertificateAuthority []FingerPrintApp `json:"certificate_authority" yaml:"certificate_authority"` + Hosting []FingerPrintApp `json:"hosting" yaml:"hosting"` + OS []FingerPrintApp `json:"os" yaml:"os"` + Softwares []FingerPrintApp `json:"softwares" yaml:"softwares"` + Databases []FingerPrintApp `json:"databases" yaml:"databases"` + Servers []FingerPrintApp `json:"servers" yaml:"servers"` + ServerExtensions []FingerPrintApp `json:"server_extensions" yaml:"server_extensions"` + AuthServices []FingerPrintApp `json:"auth_services" yaml:"auth_services"` + CDNs []FingerPrintApp `json:"cdns" yaml:"cdns"` + Caching []FingerPrintApp `json:"cache" yaml:"cache"` + Languages []FingerPrintApp `json:"languages" yaml:"languages"` + Frameworks []FingerPrintApp `json:"frameworks" yaml:"frameworks"` + SecurityServices []FingerPrintApp `json:"security_services" yaml:"security_services"` } var issue = report.Issue{ @@ -60,18 +60,18 @@ func appendIfMissing(slice []FingerPrintApp, app FingerPrintApp) []FingerPrintAp return append(slice, app) } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(DiscoverFingerPrintScanID, DiscoverFingerPrintScanName, operation) attempt, err := scan.ScanURL(operation, &securityScheme) r.AddScanAttempt(attempt) if err != nil { - return r.AddVulnerabilityReport(vulnReport.Skip()).End(), err + return r.AddIssueReport(vulnReport.Skip()).End(), err } if attempt.Err != nil { - return r.AddVulnerabilityReport(vulnReport.Skip()).End(), attempt.Err + return r.AddIssueReport(vulnReport.Skip()).End(), attempt.Err } resp := attempt.Response @@ -79,7 +79,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem wappalyzerClient, err := wappalyzer.New() if err != nil { - return r.AddVulnerabilityReport(vulnReport.Skip()).End(), err + return r.AddIssueReport(vulnReport.Skip()).End(), err } fingerprints := wappalyzerClient.FingerprintWithInfo(resp.Header, data) @@ -136,7 +136,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem } vulnReport.WithBooleanStatus(!fingerPrintIdentifier) - r.WithData(reportData).AddVulnerabilityReport(vulnReport).End() + r.WithData(reportData).AddIssueReport(vulnReport).End() return r, nil } diff --git a/scan/discover/fingerprint/fingerprint_test.go b/scan/discover/fingerprint/fingerprint_test.go index ad72bc53..a94e9239 100644 --- a/scan/discover/fingerprint/fingerprint_test.go +++ b/scan/discover/fingerprint/fingerprint_test.go @@ -28,7 +28,7 @@ func TestCheckSignatureHeader_Failed_WithServerSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.Servers)) assert.Equal(t, data.Servers[0].Name, "Apache HTTP Server:2.4.29") } @@ -49,7 +49,7 @@ func TestCheckSignatureHeader_Failed_WithOSSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.OS)) assert.Equal(t, data.OS[0].Name, "Ubuntu") } @@ -70,7 +70,7 @@ func TestCheckSignatureHeader_Failed_WithHostingSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.Hosting)) assert.Equal(t, data.Hosting[0].Name, "Hostinger") } @@ -91,7 +91,7 @@ func TestCheckSignatureHeader_Failed_WithAuthenticationSignatureHeader(t *testin require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.AuthServices)) assert.Equal(t, data.AuthServices[0].Name, "Auth0") } @@ -112,7 +112,7 @@ func TestCheckSignatureHeader_Failed_WithCDNSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.CDNs)) assert.Equal(t, data.CDNs[0].Name, "Cloudflare") } @@ -133,7 +133,7 @@ func TestCheckSignatureHeader_Failed_WithLanguageSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.Languages)) assert.Equal(t, data.Languages[0].Name, "PHP") } @@ -154,7 +154,7 @@ func TestCheckSignatureHeader_Failed_WithFrameworkSignatureHeader(t *testing.T) require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 1, len(data.Languages)) assert.Equal(t, data.Languages[0].Name, "Node.js") assert.Equal(t, 1, len(data.Frameworks)) @@ -176,7 +176,7 @@ func TestCheckSignatureHeader_Passed_WithoutDuplicate(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) assert.Equal(t, 2, len(data.Frameworks)) } @@ -194,5 +194,5 @@ func TestCheckSignatureHeader_Passed_WithoutSignatureHeader(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } diff --git a/scan/discover/utils.go b/scan/discover/utils.go index a611d5a8..d257ac46 100644 --- a/scan/discover/utils.go +++ b/scan/discover/utils.go @@ -22,7 +22,7 @@ func ExtractBaseURL(inputURL *url.URL) *url.URL { } } -func ScanURLs(scanUrls []string, operation *request.Operation, securityScheme auth.SecurityScheme, r *report.Report, vulnReport *report.VulnerabilityReport) (*report.Report, error) { +func ScanURLs(scanUrls []string, operation *request.Operation, securityScheme auth.SecurityScheme, r *report.ScanReport, vulnReport *report.IssueReport) (*report.ScanReport, error) { securitySchemes := []auth.SecurityScheme{securityScheme} base := ExtractBaseURL(&operation.URL) @@ -42,22 +42,22 @@ func ScanURLs(scanUrls []string, operation *request.Operation, securityScheme au if attempt.Response.StatusCode == http.StatusOK { // TODO: check if the response contains the expected content r.WithData(DiscoverData{ URL: attempt.Request.URL.String(), - }).AddVulnerabilityReport(vulnReport.Fail()).End() + }).AddIssueReport(vulnReport.Fail()).End() return r, nil } } - r.AddVulnerabilityReport(vulnReport.Pass()).End() + r.AddIssueReport(vulnReport.Pass()).End() return r, nil } -func CreateURLScanHandler(name string, seclistUrl string, defaultUrls []string, r *report.Report, vulnReport *report.VulnerabilityReport) func(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { +func CreateURLScanHandler(name string, seclistUrl string, defaultUrls []string, r *report.ScanReport, vulnReport *report.IssueReport) func(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { scanUrls := defaultUrls if urlsFromSeclist, err := seclist.NewSecListFromURL(name, seclistUrl); err == nil && urlsFromSeclist != nil { scanUrls = urlsFromSeclist.Items } - return func(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { + return func(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { return ScanURLs(scanUrls, operation, securityScheme, r, vulnReport) } } diff --git a/scan/discover/utils_test.go b/scan/discover/utils_test.go index 43249a08..5d38654e 100644 --- a/scan/discover/utils_test.go +++ b/scan/discover/utils_test.go @@ -52,7 +52,7 @@ func TestCreateURLScanHandler_WithTimeout(t *testing.T) { operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080", nil, client) operation.SetSecuritySchemes(securitySchemes) r := report.NewScanReport("test", "test", operation) - vulnReport := &report.VulnerabilityReport{} + vulnReport := &report.IssueReport{} handler := discover.CreateURLScanHandler("test", seclistUrl, defaultUrls, r, vulnReport) httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil)) @@ -74,7 +74,7 @@ func TestCreateURLScanHandler_Passed_WhenNotFoundURLs(t *testing.T) { operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080", nil, client) operation.SetSecuritySchemes(securitySchemes) r := report.NewScanReport("test", "test", operation) - vulnReport := &report.VulnerabilityReport{} + vulnReport := &report.IssueReport{} handler := discover.CreateURLScanHandler("test", seclistUrl, defaultUrls, r, vulnReport) httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil)) @@ -85,7 +85,7 @@ func TestCreateURLScanHandler_Passed_WhenNotFoundURLs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 2, httpmock.GetTotalCallCount()) - assert.True(t, r.Vulns[0].HasPassed()) + assert.True(t, r.Issues[0].HasPassed()) } func TestCreateURLScanHandler_Failed_WhenFoundExposedURLs(t *testing.T) { @@ -99,7 +99,7 @@ func TestCreateURLScanHandler_Failed_WhenFoundExposedURLs(t *testing.T) { operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080", nil, client) operation.SetSecuritySchemes(securitySchemes) r := report.NewScanReport("test", "test", operation) - vulnReport := &report.VulnerabilityReport{} + vulnReport := &report.IssueReport{} handler := discover.CreateURLScanHandler("test", seclistUrl, defaultUrls, r, vulnReport) @@ -111,5 +111,5 @@ func TestCreateURLScanHandler_Failed_WhenFoundExposedURLs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, r.Vulns[0].HasFailed()) + assert.True(t, r.Issues[0].HasFailed()) } diff --git a/scan/graphql/introspection_enabled/introspection_enabled.go b/scan/graphql/introspection_enabled/introspection_enabled.go index e84d9e93..fee95283 100644 --- a/scan/graphql/introspection_enabled/introspection_enabled.go +++ b/scan/graphql/introspection_enabled/introspection_enabled.go @@ -49,9 +49,9 @@ func newGetGraphqlIntrospectionRequest(client *request.Client, endpoint url.URL) return request.NewRequest(http.MethodGet, endpoint.String(), nil, client) } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { securitySchemes := []auth.SecurityScheme{securityScheme} - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(GraphqlIntrospectionScanID, GraphqlIntrospectionScanName, operation) newRequest, err := newPostGraphqlIntrospectionRequest(operation.Client, operation.URL) @@ -71,7 +71,7 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem r.AddScanAttempt(attempt).End() if attempt.Response.StatusCode == http.StatusOK { // TODO: check the GraphQL response - r.AddVulnerabilityReport(vulnReport.Fail()).End() + r.AddIssueReport(vulnReport.Fail()).End() return r, nil } @@ -92,10 +92,10 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem r.AddScanAttempt(attempt).End() if attempt.Response.StatusCode == http.StatusOK { // TODO: check the GraphQL response - r.AddVulnerabilityReport(vulnReport.Fail()).End() + r.AddIssueReport(vulnReport.Fail()).End() return r, nil } - r.AddVulnerabilityReport(vulnReport.Pass()).End() + r.AddIssueReport(vulnReport.Pass()).End() return r, nil } diff --git a/scan/graphql/introspection_enabled/introspection_enabled_test.go b/scan/graphql/introspection_enabled/introspection_enabled_test.go index 276eda15..9c742dce 100644 --- a/scan/graphql/introspection_enabled/introspection_enabled_test.go +++ b/scan/graphql/introspection_enabled/introspection_enabled_test.go @@ -25,7 +25,7 @@ func TestGraphqlIntrospectionScanHandler_Failed_WhenRespondHTTPStatusIsOK(t *tes require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } func TestGraphqlIntrospectionScanHandler_Passed_WhenNotFoundStatus(t *testing.T) { @@ -41,5 +41,5 @@ func TestGraphqlIntrospectionScanHandler_Passed_WhenNotFoundStatus(t *testing.T) require.NoError(t, err) assert.Equal(t, 2, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } diff --git a/scan/misconfiguration/http_cookies/http_cookies.go b/scan/misconfiguration/http_cookies/http_cookies.go index 746761e4..09059db6 100644 --- a/scan/misconfiguration/http_cookies/http_cookies.go +++ b/scan/misconfiguration/http_cookies/http_cookies.go @@ -103,12 +103,12 @@ var withoutExpiresIssue = report.Issue{ }, } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - httpOnlyVulnReport := report.NewVulnerabilityReport(httpNotHttpOnlyIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - notSecureVulnReport := report.NewVulnerabilityReport(notSecureIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - sameSiteNoneVulnReport := report.NewVulnerabilityReport(sameSiteNoneIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - withoutSameSiteVulnReport := report.NewVulnerabilityReport(withoutSameSiteIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - withoutExpiresVulnReport := report.NewVulnerabilityReport(withoutExpiresIssue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + httpOnlyVulnReport := report.NewIssueReport(httpNotHttpOnlyIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + notSecureVulnReport := report.NewIssueReport(notSecureIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + sameSiteNoneVulnReport := report.NewIssueReport(sameSiteNoneIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + withoutSameSiteVulnReport := report.NewIssueReport(withoutSameSiteIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + withoutExpiresVulnReport := report.NewIssueReport(withoutExpiresIssue).WithOperation(operation).WithSecurityScheme(securityScheme) attempt, err := scan.ScanURL(operation, &securityScheme) r := report.NewScanReport(HTTPCookiesScanID, HTTPCookiesScanName, operation) @@ -119,19 +119,19 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem // Detect every cookies insecure practices for _, cookie := range attempt.Response.Cookies() { - r.AddVulnerabilityReport(notSecureVulnReport.Clone().WithBooleanStatus(cookie.Secure)) - r.AddVulnerabilityReport(httpOnlyVulnReport.Clone().WithBooleanStatus(cookie.HttpOnly)) - r.AddVulnerabilityReport(sameSiteNoneVulnReport.Clone().WithBooleanStatus(cookie.SameSite != http.SameSiteNoneMode)) - r.AddVulnerabilityReport(withoutSameSiteVulnReport.Clone().WithBooleanStatus(cookie.SameSite != 0)) - r.AddVulnerabilityReport(withoutExpiresVulnReport.Clone().WithBooleanStatus(!cookie.Expires.IsZero())) + r.AddIssueReport(notSecureVulnReport.Clone().WithBooleanStatus(cookie.Secure)) + r.AddIssueReport(httpOnlyVulnReport.Clone().WithBooleanStatus(cookie.HttpOnly)) + r.AddIssueReport(sameSiteNoneVulnReport.Clone().WithBooleanStatus(cookie.SameSite != http.SameSiteNoneMode)) + r.AddIssueReport(withoutSameSiteVulnReport.Clone().WithBooleanStatus(cookie.SameSite != 0)) + r.AddIssueReport(withoutExpiresVulnReport.Clone().WithBooleanStatus(!cookie.Expires.IsZero())) } if len(attempt.Response.Cookies()) == 0 { - r.AddVulnerabilityReport(notSecureVulnReport.Clone().Skip()) - r.AddVulnerabilityReport(httpOnlyVulnReport.Clone().Skip()) - r.AddVulnerabilityReport(sameSiteNoneVulnReport.Clone().Skip()) - r.AddVulnerabilityReport(withoutSameSiteVulnReport.Clone().Skip()) - r.AddVulnerabilityReport(withoutExpiresVulnReport.Clone().Skip()) + r.AddIssueReport(notSecureVulnReport.Clone().Skip()) + r.AddIssueReport(httpOnlyVulnReport.Clone().Skip()) + r.AddIssueReport(sameSiteNoneVulnReport.Clone().Skip()) + r.AddIssueReport(withoutSameSiteVulnReport.Clone().Skip()) + r.AddIssueReport(withoutExpiresVulnReport.Clone().Skip()) } return r, nil diff --git a/scan/misconfiguration/http_cookies/http_cookies_test.go b/scan/misconfiguration/http_cookies/http_cookies_test.go index 509eac0c..52f023c4 100644 --- a/scan/misconfiguration/http_cookies/http_cookies_test.go +++ b/scan/misconfiguration/http_cookies/http_cookies_test.go @@ -26,9 +26,9 @@ func TestHTTPCookiesScanHandler_Skipped_WhenNoCookies(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.False(t, report.HasFailedVulnerabilityReport()) - assert.True(t, report.GetVulnerabilityReports()[0].HasBeenSkipped()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.False(t, report.HasFailedIssueReport()) + assert.True(t, report.GetIssueReports()[0].HasBeenSkipped()) } func TestHTTPCookiesScanHandler_Passed_WhenNoUnsecurePractices(t *testing.T) { @@ -56,8 +56,8 @@ func TestHTTPCookiesScanHandler_Passed_WhenNoUnsecurePractices(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.False(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.False(t, report.HasFailedIssueReport()) } func TestHTTPCookiesScanHandler_Failed_WhenNotHttpOnly(t *testing.T) { @@ -85,8 +85,8 @@ func TestHTTPCookiesScanHandler_Failed_WhenNotHttpOnly(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPCookiesScanHandlerFailed_WhenNotSecure(t *testing.T) { @@ -114,8 +114,8 @@ func TestHTTPCookiesScanHandlerFailed_WhenNotSecure(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPCookiesScanHandler_Failed_WhenSameSiteNone(t *testing.T) { @@ -143,8 +143,8 @@ func TestHTTPCookiesScanHandler_Failed_WhenSameSiteNone(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPCookiesScanHandler_Failed_WhithoutSameSite(t *testing.T) { @@ -171,8 +171,8 @@ func TestHTTPCookiesScanHandler_Failed_WhithoutSameSite(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPCookiesScanHandler_Failed_WhenExpiresNotSet(t *testing.T) { @@ -200,6 +200,6 @@ func TestHTTPCookiesScanHandler_Failed_WhenExpiresNotSet(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 5, len(report.GetVulnerabilityReports())) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.Equal(t, 5, len(report.GetIssueReports())) + assert.True(t, report.HasFailedIssueReport()) } diff --git a/scan/misconfiguration/http_headers/http_headers.go b/scan/misconfiguration/http_headers/http_headers.go index e6ac35df..d1cbd2d0 100644 --- a/scan/misconfiguration/http_headers/http_headers.go +++ b/scan/misconfiguration/http_headers/http_headers.go @@ -156,14 +156,14 @@ func CheckCSPFrameAncestors(cspHeader string) bool { return false } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - contentOptionsMissing := report.NewVulnerabilityReport(contentOptionsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - corsMissing := report.NewVulnerabilityReport(corsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - corsWildcard := report.NewVulnerabilityReport(corsWildcardIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - cspFrameAncestorsMissing := report.NewVulnerabilityReport(cspFrameAncestorsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - cspMissing := report.NewVulnerabilityReport(cspMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - frameOptionsMissing := report.NewVulnerabilityReport(frameOptionsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) - hstsMissing := report.NewVulnerabilityReport(hstsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + contentOptionsMissing := report.NewIssueReport(contentOptionsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + corsMissing := report.NewIssueReport(corsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + corsWildcard := report.NewIssueReport(corsWildcardIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + cspFrameAncestorsMissing := report.NewIssueReport(cspFrameAncestorsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + cspMissing := report.NewIssueReport(cspMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + frameOptionsMissing := report.NewIssueReport(frameOptionsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + hstsMissing := report.NewIssueReport(hstsMissingIssue).WithOperation(operation).WithSecurityScheme(securityScheme) attempt, err := scan.ScanURL(operation, &securityScheme) r := report.NewScanReport(HTTPHeadersScanID, HTTPHeadersScanName, operation) @@ -173,22 +173,22 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem r.AddScanAttempt(attempt).End() cspHeader := attempt.Response.Header.Get(CSPHTTPHeader) - r.AddVulnerabilityReport(cspMissing.Clone().WithBooleanStatus(cspHeader != "")) - r.AddVulnerabilityReport(cspFrameAncestorsMissing.Clone().WithBooleanStatus(CheckCSPFrameAncestors(cspHeader))) + r.AddIssueReport(cspMissing.Clone().WithBooleanStatus(cspHeader != "")) + r.AddIssueReport(cspFrameAncestorsMissing.Clone().WithBooleanStatus(CheckCSPFrameAncestors(cspHeader))) allowOrigin := attempt.Response.Header.Get(CORSOriginHTTPHeader) isCorsMissing := allowOrigin == "" - r.AddVulnerabilityReport(corsMissing.Clone().WithBooleanStatus(!isCorsMissing)) + r.AddIssueReport(corsMissing.Clone().WithBooleanStatus(!isCorsMissing)) if isCorsMissing { - r.AddVulnerabilityReport(corsWildcard.Clone().Skip()) + r.AddIssueReport(corsWildcard.Clone().Skip()) } else { - r.AddVulnerabilityReport(corsWildcard.Clone().WithBooleanStatus(allowOrigin != "*")) + r.AddIssueReport(corsWildcard.Clone().WithBooleanStatus(allowOrigin != "*")) } - r.AddVulnerabilityReport(hstsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(HSTSHTTPHeader) != "")) - r.AddVulnerabilityReport(contentOptionsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(XContentTypeOptionsHTTPHeader) != "")) - r.AddVulnerabilityReport(frameOptionsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(XFrameOptionsHTTPHeader) != "")) + r.AddIssueReport(hstsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(HSTSHTTPHeader) != "")) + r.AddIssueReport(contentOptionsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(XContentTypeOptionsHTTPHeader) != "")) + r.AddIssueReport(frameOptionsMissing.Clone().WithBooleanStatus(attempt.Response.Header.Get(XFrameOptionsHTTPHeader) != "")) return r, nil } diff --git a/scan/misconfiguration/http_headers/http_headers_test.go b/scan/misconfiguration/http_headers/http_headers_test.go index ab5f91a1..69a1b6c0 100644 --- a/scan/misconfiguration/http_headers/http_headers_test.go +++ b/scan/misconfiguration/http_headers/http_headers_test.go @@ -37,14 +37,14 @@ func TestHTTPHeadersScanHandler_Passed(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.Equal(t, 7, len(report.Vulns)) - assert.False(t, report.HasFailedVulnerabilityReport()) - assert.True(t, report.Vulns[0].HasPassed()) - assert.True(t, report.Vulns[2].HasPassed()) - assert.True(t, report.Vulns[3].HasPassed()) - assert.True(t, report.Vulns[4].HasPassed()) - assert.True(t, report.Vulns[5].HasPassed()) - assert.True(t, report.Vulns[6].HasPassed()) + assert.Equal(t, 7, len(report.Issues)) + assert.False(t, report.HasFailedIssueReport()) + assert.True(t, report.Issues[0].HasPassed()) + assert.True(t, report.Issues[2].HasPassed()) + assert.True(t, report.Issues[3].HasPassed()) + assert.True(t, report.Issues[4].HasPassed()) + assert.True(t, report.Issues[5].HasPassed()) + assert.True(t, report.Issues[6].HasPassed()) } func TestHTTPHeadersBestPracticesWithoutCSPScanHandler(t *testing.T) { @@ -63,7 +63,7 @@ func TestHTTPHeadersBestPracticesWithoutCSPScanHandler(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithoutFrameAncestorsCSPDirectiveScanHandler(t *testing.T) { @@ -82,7 +82,7 @@ func TestHTTPHeadersBestPracticesWithoutFrameAncestorsCSPDirectiveScanHandler(t require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithNotNoneFrameAncestorsCSPDirectiveScanHandler(t *testing.T) { @@ -101,7 +101,7 @@ func TestHTTPHeadersBestPracticesWithNotNoneFrameAncestorsCSPDirectiveScanHandle require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithoutCORSScanHandler(t *testing.T) { @@ -120,7 +120,7 @@ func TestHTTPHeadersBestPracticesWithoutCORSScanHandler(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithPermissiveCORSScanHandler(t *testing.T) { @@ -139,7 +139,7 @@ func TestHTTPHeadersBestPracticesWithPermissiveCORSScanHandler(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithoutHSTSScanHandler(t *testing.T) { @@ -158,7 +158,7 @@ func TestHTTPHeadersBestPracticesWithoutHSTSScanHandler(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithoutXContentTypeOptionsScanHandler(t *testing.T) { @@ -177,7 +177,7 @@ func TestHTTPHeadersBestPracticesWithoutXContentTypeOptionsScanHandler(t *testin require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } func TestHTTPHeadersBestPracticesWithoutXFrameOptionsScanHandler(t *testing.T) { @@ -196,5 +196,5 @@ func TestHTTPHeadersBestPracticesWithoutXFrameOptionsScanHandler(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.HasFailedVulnerabilityReport()) + assert.True(t, report.HasFailedIssueReport()) } diff --git a/scan/misconfiguration/http_trace/http_trace_method.go b/scan/misconfiguration/http_trace/http_trace_method.go index be453f76..b9d66a39 100644 --- a/scan/misconfiguration/http_trace/http_trace_method.go +++ b/scan/misconfiguration/http_trace/http_trace_method.go @@ -31,15 +31,15 @@ var issue = report.Issue{ }, } -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(HTTPTraceScanID, HTTPTraceScanName, operation) newOperation := operation.Clone() newOperation.Method = http.MethodTrace attempt, err := scan.ScanURL(newOperation, &securityScheme) - r.AddScanAttempt(attempt).End().AddVulnerabilityReport(vulnReport.WithBooleanStatus(err != nil || attempt.Response.StatusCode != http.StatusOK)) + r.AddScanAttempt(attempt).End().AddIssueReport(vulnReport.WithBooleanStatus(err != nil || attempt.Response.StatusCode != http.StatusOK)) return r, nil } diff --git a/scan/misconfiguration/http_trace/http_trace_method_test.go b/scan/misconfiguration/http_trace/http_trace_method_test.go index 4582f618..ba1542a3 100644 --- a/scan/misconfiguration/http_trace/http_trace_method_test.go +++ b/scan/misconfiguration/http_trace/http_trace_method_test.go @@ -24,7 +24,7 @@ func TestHTTPTraceMethodScanHandler_Passed_WhenNotOKResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestHTTPTraceMethodScanHandler_Failed_WhenTraceIsEnabled(t *testing.T) { @@ -39,5 +39,5 @@ func TestHTTPTraceMethodScanHandler_Failed_WhenTraceIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/misconfiguration/http_track/http_track_method.go b/scan/misconfiguration/http_track/http_track_method.go index bcce134a..94834e5f 100644 --- a/scan/misconfiguration/http_track/http_track_method.go +++ b/scan/misconfiguration/http_track/http_track_method.go @@ -33,15 +33,15 @@ var issue = report.Issue{ const TrackMethod = "TRACK" -func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.Report, error) { - vulnReport := report.NewVulnerabilityReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(HTTPTrackScanID, HTTPTrackScanName, operation) newOperation := operation.Clone() newOperation.Method = TrackMethod attempt, err := scan.ScanURL(newOperation, &securityScheme) - r.AddScanAttempt(attempt).End().AddVulnerabilityReport(vulnReport.WithBooleanStatus(err != nil || attempt.Response.StatusCode != http.StatusOK)) + r.AddScanAttempt(attempt).End().AddIssueReport(vulnReport.WithBooleanStatus(err != nil || attempt.Response.StatusCode != http.StatusOK)) return r, nil } diff --git a/scan/misconfiguration/http_track/http_track_method_test.go b/scan/misconfiguration/http_track/http_track_method_test.go index 3d269a81..f74800b7 100644 --- a/scan/misconfiguration/http_track/http_track_method_test.go +++ b/scan/misconfiguration/http_track/http_track_method_test.go @@ -24,7 +24,7 @@ func TestHTTPTrackMethodScanHandler_Passed_WhenNotOKResponse(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasPassed()) + assert.True(t, report.Issues[0].HasPassed()) } func TestHTTPTrackMethodScanHandler_Failed_WhenTrackIsEnabled(t *testing.T) { @@ -39,5 +39,5 @@ func TestHTTPTrackMethodScanHandler_Failed_WhenTrackIsEnabled(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, httpmock.GetTotalCallCount()) - assert.True(t, report.Vulns[0].HasFailed()) + assert.True(t, report.Issues[0].HasFailed()) } diff --git a/scan/operation_scan.go b/scan/operation_scan.go index ac0352cc..5d0b3800 100644 --- a/scan/operation_scan.go +++ b/scan/operation_scan.go @@ -6,7 +6,7 @@ import ( "github.com/cerberauth/vulnapi/report" ) -type OperationScanHandlerFunc func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) +type OperationScanHandlerFunc func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) type OperationScanHandler struct { ID string diff --git a/scan/operation_scan_test.go b/scan/operation_scan_test.go index b37afd82..044ecc5e 100644 --- a/scan/operation_scan_test.go +++ b/scan/operation_scan_test.go @@ -11,8 +11,8 @@ import ( ) func TestNewOperationScanHandler(t *testing.T) { - handlerFunc := func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, nil + handlerFunc := func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + return &report.ScanReport{ID: "test-report"}, nil } handlerID := "test-handler" diff --git a/scan/scan_test.go b/scan/scan_test.go index 3ab109aa..c49541ff 100644 --- a/scan/scan_test.go +++ b/scan/scan_test.go @@ -73,7 +73,7 @@ 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(scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { + s.AddOperationScanHandler(scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { return nil, nil })) @@ -91,15 +91,15 @@ func TestScanExecuteWithNoHandlers(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 0, len(reporter.Reports)) + assert.Equal(t, 0, len(reporter.GetScanReports())) } 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.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -107,8 +107,8 @@ func TestScanExecuteWithHandler(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 1, len(reporter.Reports)) - assert.Equal(t, "test-report", reporter.Reports[0].ID) + assert.Equal(t, 1, len(reporter.GetScanReports())) + assert.Equal(t, "test-report", reporter.GetScanReports()[0].ID) } func TestScanExecuteWithIncludeScans(t *testing.T) { @@ -117,8 +117,8 @@ func TestScanExecuteWithIncludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ IncludeScans: []string{"test-handler"}, }) - handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -126,8 +126,8 @@ func TestScanExecuteWithIncludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 1, len(reporter.Reports)) - assert.Equal(t, "test-report", reporter.Reports[0].ID) + assert.Equal(t, 1, len(reporter.GetScanReports())) + assert.Equal(t, "test-report", reporter.GetScanReports()[0].ID) } func TestScanExecuteWithEmptyStringIncludeScans(t *testing.T) { @@ -136,8 +136,8 @@ func TestScanExecuteWithEmptyStringIncludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ IncludeScans: []string{""}, }) - handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -145,8 +145,8 @@ func TestScanExecuteWithEmptyStringIncludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 1, len(reporter.Reports)) - assert.Equal(t, "test-report", reporter.Reports[0].ID) + assert.Equal(t, 1, len(reporter.GetScanReports())) + assert.Equal(t, "test-report", reporter.GetScanReports()[0].ID) } func TestScanExecuteWithMatchStringIncludeScans(t *testing.T) { @@ -155,8 +155,8 @@ func TestScanExecuteWithMatchStringIncludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ IncludeScans: []string{"category.*"}, }) - handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -164,8 +164,8 @@ func TestScanExecuteWithMatchStringIncludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 1, len(reporter.Reports)) - assert.Equal(t, "test-report", reporter.Reports[0].ID) + assert.Equal(t, 1, len(reporter.GetScanReports())) + assert.Equal(t, "test-report", reporter.GetScanReports()[0].ID) } func TestScanExecuteWithWrongMatchStringIncludeScans(t *testing.T) { @@ -174,8 +174,8 @@ func TestScanExecuteWithWrongMatchStringIncludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ IncludeScans: []string{"wrong-category.*"}, }) - handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -183,7 +183,7 @@ func TestScanExecuteWithWrongMatchStringIncludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 0, len(reporter.Reports)) + assert.Equal(t, 0, len(reporter.GetScanReports())) } func TestScanExecuteWithExcludeScans(t *testing.T) { @@ -192,8 +192,8 @@ func TestScanExecuteWithExcludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ ExcludeScans: []string{"test-handler"}, }) - handler := scan.NewOperationScanHandler("test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -201,7 +201,7 @@ func TestScanExecuteWithExcludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 0, len(reporter.Reports)) + assert.Equal(t, 0, len(reporter.GetScanReports())) } func TestScanExecuteWithMatchStringExcludeScans(t *testing.T) { @@ -210,8 +210,8 @@ func TestScanExecuteWithMatchStringExcludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ ExcludeScans: []string{"category.*"}, }) - handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -219,7 +219,7 @@ func TestScanExecuteWithMatchStringExcludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 0, len(reporter.Reports)) + assert.Equal(t, 0, len(reporter.GetScanReports())) } func TestScanExecuteWithWrongMatchStringExcludeScans(t *testing.T) { @@ -228,8 +228,8 @@ func TestScanExecuteWithWrongMatchStringExcludeScans(t *testing.T) { s, _ := scan.NewScan(operations, &scan.ScanOptions{ ExcludeScans: []string{"wrong-category.*"}, }) - handler := scan.NewOperationScanHandler("category.test-handler", func(operation *request.Operation, ss auth.SecurityScheme) (*report.Report, error) { - return &report.Report{ID: "test-report"}, 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) @@ -237,6 +237,6 @@ func TestScanExecuteWithWrongMatchStringExcludeScans(t *testing.T) { require.NoError(t, err) assert.Empty(t, errors) - assert.Equal(t, 1, len(reporter.Reports)) - assert.Equal(t, "test-report", reporter.Reports[0].ID) + assert.Equal(t, 1, len(reporter.GetScanReports())) + assert.Equal(t, "test-report", reporter.GetScanReports()[0].ID) } diff --git a/scenario/graphql.go b/scenario/graphql.go index ac348b8d..e8ac185c 100644 --- a/scenario/graphql.go +++ b/scenario/graphql.go @@ -5,6 +5,7 @@ import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/report" "github.com/cerberauth/vulnapi/scan" introspectionenabled "github.com/cerberauth/vulnapi/scan/graphql/introspection_enabled" ) @@ -37,6 +38,14 @@ func NewGraphQLScan(url string, client *request.Client, opts *scan.ScanOptions) return nil, err } + if opts == nil { + opts = &scan.ScanOptions{} + } + + if opts.Reporter == nil { + opts.Reporter = report.NewReporterWithGraphQL(url, securitySchemes) + } + operations := request.Operations{operation} graphqlScan, err := scan.NewScan(operations, opts) if err != nil { diff --git a/scenario/openapi.go b/scenario/openapi.go index 243648a9..daa82997 100644 --- a/scenario/openapi.go +++ b/scenario/openapi.go @@ -4,6 +4,7 @@ import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" "github.com/cerberauth/vulnapi/openapi" + "github.com/cerberauth/vulnapi/report" "github.com/cerberauth/vulnapi/scan" ) @@ -29,6 +30,14 @@ func NewOpenAPIScan(openapi *openapi.OpenAPI, securitySchemesValues *auth.Securi return nil, err } + if opts == nil { + opts = &scan.ScanOptions{} + } + + if opts.Reporter == nil { + opts.Reporter = report.NewReporterWithOpenAPIDoc(openapi.Doc, operations) + } + openapiScan, err := scan.NewScan(operations, opts) if err != nil { return nil, err diff --git a/scenario/url.go b/scenario/url.go index 023c8f1a..50534f79 100644 --- a/scenario/url.go +++ b/scenario/url.go @@ -5,6 +5,7 @@ import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/report" "github.com/cerberauth/vulnapi/scan" discoverablegraphql "github.com/cerberauth/vulnapi/scan/discover/discoverable_graphql" discoverableopenapi "github.com/cerberauth/vulnapi/scan/discover/discoverable_openapi" @@ -31,6 +32,7 @@ func NewURLScan(method string, url string, data string, client *request.Client, url = addDefaultProtocolWhenMissing(url) operation, err := request.NewOperation(method, url, body, client) + operation.GenerateID() if err != nil { return nil, err } @@ -40,6 +42,14 @@ func NewURLScan(method string, url string, data string, client *request.Client, return nil, err } + if opts == nil { + opts = &scan.ScanOptions{} + } + + if opts.Reporter == nil { + opts.Reporter = report.NewReporterWithCurl(method, url, data, client.Header, client.Cookies, securitySchemes) + } + operations := request.Operations{operation} urlScan, err := scan.NewScan(operations, opts) if err != nil {