From ab9c1c89cfe557fe9ce6042dd5e2fc8c36062719 Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Sun, 6 Oct 2024 17:07:52 +0200 Subject: [PATCH 1/2] ci: add golangci lint --- .github/workflows/ci.yml | 11 +++++++++-- .golangci.yml | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6f1502..a6a250f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,13 @@ concurrency: cancel-in-progress: true jobs: - build: + test: runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: - uses: actions/checkout@v4 @@ -29,6 +33,9 @@ jobs: with: go-version: ${{ env.GO_VERSION }} + - name: Lint + uses: golangci/golangci-lint-action@v6 + - name: Build run: go build -v ./... @@ -41,7 +48,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} publish: - needs: build + needs: test runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..ed65a7a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,26 @@ +linters: + enable: + - errcheck + - goconst + - gocritic + - gofmt + - goimports + - gosec + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + +linters-settings: + gosec: + excludes: + - G101 + - G107 + +issues: + exclude-files: + - ".+_test.go" + + From 553af783b31f17abc7f133605a547e432933c01d Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Sun, 6 Oct 2024 17:53:10 +0200 Subject: [PATCH 2/2] fix: fix or ignore lint errors --- cmd/discover/api.go | 2 ++ cmd/discover/domain.go | 2 ++ cmd/root.go | 5 ++- cmd/scan/curl.go | 8 ++++- cmd/scan/graphql.go | 8 ++++- cmd/scan/openapi.go | 2 ++ internal/analytics/analytics.go | 8 +++-- internal/cmd/output.go | 7 ++-- internal/cmd/printtable/report_table.go | 9 ++--- internal/request/client.go | 2 +- internal/request/operation.go | 18 ++++++---- internal/request/operation_test.go | 3 +- openapi/operation.go | 6 ++-- openapi/param.go | 36 ++++++++++--------- openapi/security_scheme.go | 7 ++-- report/vuln.go | 13 +++---- .../introspection_enabled.go | 12 +++++-- 17 files changed, 98 insertions(+), 50 deletions(-) diff --git a/cmd/discover/api.go b/cmd/discover/api.go index 1ac191c..62e78a6 100644 --- a/cmd/discover/api.go +++ b/cmd/discover/api.go @@ -44,10 +44,12 @@ func NewAPICmd() (apiCmd *cobra.Command) { var bar *progressbar.ProgressBar if !internalCmd.GetNoProgress() { bar = internalCmd.NewProgressBar(len(s.GetOperationsScans())) + // nolint:errcheck defer bar.Finish() } reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { if bar != nil { + // nolint:errcheck bar.Add(1) } }) diff --git a/cmd/discover/domain.go b/cmd/discover/domain.go index 17b80f1..03b0b9d 100644 --- a/cmd/discover/domain.go +++ b/cmd/discover/domain.go @@ -50,10 +50,12 @@ func NewDomainCmd() (domainCmd *cobra.Command) { var bar *progressbar.ProgressBar if !internalCmd.GetNoProgress() { bar = internalCmd.NewProgressBar(len(s.GetOperationsScans())) + // nolint:errcheck defer bar.Finish() } reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { if bar != nil { + // nolint:errcheck bar.Add(1) } }) diff --git a/cmd/root.go b/cmd/root.go index 7c8d921..9ad0a8c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,7 +31,10 @@ func NewRootCmd(projectVersion string) (cmd *cobra.Command) { PersistentPreRun: func(cmd *cobra.Command, args []string) { if !sqaOptOut { ctx := cmd.Context() - analytics.NewAnalytics(ctx, projectVersion) + _, err := analytics.NewAnalytics(ctx, projectVersion) + if err != nil { + fmt.Println("Failed to initialize analytics:", err) + } } }, PersistentPostRun: func(cmd *cobra.Command, args []string) { diff --git a/cmd/scan/curl.go b/cmd/scan/curl.go index d3990e7..ce67d3f 100644 --- a/cmd/scan/curl.go +++ b/cmd/scan/curl.go @@ -53,10 +53,12 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { var bar *progressbar.ProgressBar if !internalCmd.GetNoProgress() { bar = internalCmd.NewProgressBar(len(s.GetOperationsScans())) + // nolint:errcheck defer bar.Finish() } reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { if bar != nil { + // nolint:errcheck bar.Add(1) } }) @@ -66,7 +68,11 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) { } internalCmd.TrackScanReport(ctx, tracer, reporter) - internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), reporter) + err = internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), 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 cba3ef1..0bff874 100644 --- a/cmd/scan/graphql.go +++ b/cmd/scan/graphql.go @@ -45,10 +45,12 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { var bar *progressbar.ProgressBar if !internalCmd.GetNoProgress() { bar = internalCmd.NewProgressBar(len(s.GetOperationsScans())) + // nolint:errcheck defer bar.Finish() } reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { if bar != nil { + // nolint:errcheck bar.Add(1) } }) @@ -58,7 +60,11 @@ func NewGraphQLScanCmd() (scanCmd *cobra.Command) { } internalCmd.TrackScanReport(ctx, tracer, reporter) - internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), reporter) + err = internalCmd.PrintOrExportReport(internalCmd.GetOutputFormat(), internalCmd.GetOutputTransport(), 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 75fef89..72edeeb 100644 --- a/cmd/scan/openapi.go +++ b/cmd/scan/openapi.go @@ -87,10 +87,12 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) { var bar *progressbar.ProgressBar if !internalCmd.GetNoProgress() { bar = internalCmd.NewProgressBar(len(s.GetOperationsScans())) + // nolint:errcheck defer bar.Finish() } reporter, _, err := s.Execute(func(operationScan *scan.OperationScan) { if bar != nil { + // nolint:errcheck bar.Add(1) } }) diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index 33909df..110ecb9 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -23,6 +23,10 @@ func NewAnalytics(ctx context.Context, projectVersion string) (*sdktrace.TracerP return tracerProvider, err } -func Close() { - tracerProvider.Shutdown(context.Background()) +func Close() error { + if tracerProvider == nil { + return nil + } + + return tracerProvider.Shutdown(context.Background()) } diff --git a/internal/cmd/output.go b/internal/cmd/output.go index 4fe8cb6..d031049 100644 --- a/internal/cmd/output.go +++ b/internal/cmd/output.go @@ -17,11 +17,12 @@ func PrintOrExportReport(format string, transport string, report *report.Reporte } var outputMessage string - if !report.HasVulnerability() { + switch { + case !report.HasVulnerability(): outputMessage = "Success: No vulnerabilities detected!" - } else if report.HasHighRiskOrHigherSeverityVulnerability() { + case report.HasHighRiskOrHigherSeverityVulnerability(): outputMessage = "Error: There are some high-risk issues. It's advised to take immediate action." - } else { + default: outputMessage = "Warning: There are some issues. It's advised to take action." } diff --git a/internal/cmd/printtable/report_table.go b/internal/cmd/printtable/report_table.go index 9a2f6cf..027bf2b 100644 --- a/internal/cmd/printtable/report_table.go +++ b/internal/cmd/printtable/report_table.go @@ -64,13 +64,14 @@ func NewFullScanVulnerabilityReports(reports []*report.Report) []*ScanVulnerabil } func severityTableColor(v *report.VulnerabilityReport) int { - if v.IsLowRiskSeverity() || v.IsInfoRiskSeverity() { + switch { + case v.IsLowRiskSeverity() || v.IsInfoRiskSeverity(): return tablewriter.BgBlueColor - } else if v.IsMediumRiskSeverity() { + case v.IsMediumRiskSeverity(): return tablewriter.BgYellowColor - } else if v.IsHighRiskSeverity() { + case v.IsHighRiskSeverity(): return tablewriter.BgRedColor - } else if v.IsCriticalRiskSeverity() { + case v.IsCriticalRiskSeverity(): return tablewriter.BgHiRedColor } diff --git a/internal/request/client.go b/internal/request/client.go index 88a8783..acc3edf 100644 --- a/internal/request/client.go +++ b/internal/request/client.go @@ -44,7 +44,7 @@ func NewClient(opts NewClientOptions) *Client { opts.Cookies = []*http.Cookie{} } - var proxy func(*http.Request) (*url.URL, error) = nil + var proxy func(*http.Request) (*url.URL, error) if opts.ProxyURL != nil && opts.ProxyURL.String() != "" { proxy = http.ProxyURL(opts.ProxyURL) } else { diff --git a/internal/request/operation.go b/internal/request/operation.go index 5aa9925..e224e98 100644 --- a/internal/request/operation.go +++ b/internal/request/operation.go @@ -28,7 +28,7 @@ type Operation struct { *Client `json:"-" yaml:"-"` Method string `json:"method" yaml:"method"` - URL url.URL `json:"url,string" yaml:"url,string"` + 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"` @@ -63,11 +63,12 @@ func NewOperation(method string, operationUrl string, body *bytes.Buffer, client func (operation *Operation) IsReachable() error { host := operation.URL.Host if _, _, err := net.SplitHostPort(host); err != nil { - if operation.URL.Scheme == "http" { + switch operation.URL.Scheme { + case "http": host += ":80" - } else if operation.URL.Scheme == "https" { + case "https": host += ":443" - } else { + default: return errors.New("unsupported scheme") } } @@ -76,11 +77,14 @@ func (operation *Operation) IsReachable() error { return err } -func NewOperationFromRequest(r *Request) *Operation { +func NewOperationFromRequest(r *Request) (*Operation, error) { var body bytes.Buffer if r.Body != nil { tee := io.TeeReader(r.Body, &body) - io.ReadAll(tee) + _, err := io.ReadAll(tee) + if err != nil { + return nil, err + } } return &Operation{ @@ -91,7 +95,7 @@ func NewOperationFromRequest(r *Request) *Operation { Body: &body, SecuritySchemes: []auth.SecurityScheme{auth.NewNoAuthSecurityScheme()}, - } + }, nil } func (operation *Operation) WithOpenapiOperation(openapiOperation openapi3.Operation) *Operation { diff --git a/internal/request/operation_test.go b/internal/request/operation_test.go index 26d5a3a..1879eb1 100644 --- a/internal/request/operation_test.go +++ b/internal/request/operation_test.go @@ -83,8 +83,9 @@ func TestNewOperationFromRequest(t *testing.T) { r.WithHeader(header) cookies := []*http.Cookie{} r.WithCookies(cookies) - operation := request.NewOperationFromRequest(r) + operation, err := request.NewOperationFromRequest(r) + assert.NoError(t, err) assert.Equal(t, r.URL.String(), operation.URL.String()) assert.Equal(t, r.Method, operation.Method) } diff --git a/openapi/operation.go b/openapi/operation.go index 5c0bdab..3496c17 100644 --- a/openapi/operation.go +++ b/openapi/operation.go @@ -81,11 +81,13 @@ func (openapi *OpenAPI) Operations(client *request.Client, securitySchemes auth. } } - body := bytes.NewBuffer(nil) + var body *bytes.Buffer + var mediaType string if o.RequestBody != nil { - mediaType := "application/json" body, mediaType = getRequestBodyValue(o.RequestBody.Value) header.Set("Content-Type", mediaType) + } else { + body = bytes.NewBuffer(nil) } operation, err := request.NewOperation(method, operationUrl.String(), body, client) diff --git a/openapi/param.go b/openapi/param.go index aeffccf..014ae85 100644 --- a/openapi/param.go +++ b/openapi/param.go @@ -11,13 +11,14 @@ import ( func getParameterValue(param *openapi3.Parameter) string { if param.Schema != nil { value := getSchemaValue(param.Schema.Value) - if param.Schema.Value.Type.Is("string") { + switch { + case param.Schema.Value.Type.Is("string"): return value.(string) - } else if param.Schema.Value.Type.Is("number") { + case param.Schema.Value.Type.Is("number"): return strconv.FormatFloat(value.(float64), 'f', -1, 64) - } else if param.Schema.Value.Type.Is("integer") { + case param.Schema.Value.Type.Is("integer"): return strconv.Itoa(value.(int)) - } else if param.Schema.Value.Type.Is("boolean") { + case param.Schema.Value.Type.Is("boolean"): return strconv.FormatBool(value.(bool)) } } @@ -27,15 +28,16 @@ func getParameterValue(param *openapi3.Parameter) string { func mapRequestBodyFakeValueToJSON(schema *openapi3.Schema, fakeValue interface{}) *bytes.Buffer { jsonResponse := []byte("{}") - if schema.Type.Is("string") { + switch { + case schema.Type.Is("string"): jsonResponse = []byte("\"" + fakeValue.(string) + "\"") - } else if schema.Type.Is("number") { + case schema.Type.Is("number"): jsonResponse = []byte(strconv.FormatFloat(fakeValue.(float64), 'f', -1, 64)) - } else if schema.Type.Is("integer") { + case schema.Type.Is("integer"): jsonResponse = []byte(strconv.Itoa(fakeValue.(int))) - } else if schema.Type.Is("boolean") { + case schema.Type.Is("boolean"): jsonResponse = []byte(strconv.FormatBool(fakeValue.(bool))) - } else if schema.Type.Is("array") { + case schema.Type.Is("array"): jsonResponse = []byte("[") for i, value := range fakeValue.([]interface{}) { if i > 0 { @@ -44,7 +46,7 @@ func mapRequestBodyFakeValueToJSON(schema *openapi3.Schema, fakeValue interface{ jsonResponse = append(jsonResponse, mapRequestBodyFakeValueToJSON(schema.Items.Value, value).Bytes()...) } jsonResponse = append(jsonResponse, ']') - } else if schema.Type.Is("object") { + case schema.Type.Is("object"): jsonResponse = []byte("{") i := 0 for key, value := range fakeValue.(map[string]interface{}) { @@ -57,7 +59,6 @@ func mapRequestBodyFakeValueToJSON(schema *openapi3.Schema, fakeValue interface{ } jsonResponse = append(jsonResponse, '}') } - return bytes.NewBuffer(jsonResponse) } @@ -69,6 +70,8 @@ func getRequestBodyValue(requestBody *openapi3.RequestBody) (*bytes.Buffer, stri switch mediaType { case "application/json": return mapRequestBodyFakeValueToJSON(mediaTypeValue.Schema.Value, body), "application/json" + default: + return bytes.NewBuffer([]byte(body.(string))), mediaType } } } @@ -85,19 +88,20 @@ func getSchemaValue(schema *openapi3.Schema) interface{} { } // if there is no example generate random param - if schema.Type.Is("number") || schema.Type.Is("integer") { + switch { + case schema.Type.Is("number") || schema.Type.Is("integer"): return gofakeit.Number(0, 10) - } else if schema.Type.Is("boolean") { + case schema.Type.Is("boolean"): return gofakeit.Bool() - } else if schema.Type.Is("array") { + case schema.Type.Is("array"): return []interface{}{getSchemaValue(schema.Items.Value)} - } else if schema.Type.Is("object") { + case schema.Type.Is("object"): object := map[string]interface{}{} for key, value := range schema.Properties { object[key] = getSchemaValue(value.Value) } return object - } else if schema.Type.Is("string") { + case schema.Type.Is("string"): switch schema.Format { case "date": return gofakeit.Date().Format("2006-01-02") diff --git a/openapi/security_scheme.go b/openapi/security_scheme.go index 57100b3..d796b19 100644 --- a/openapi/security_scheme.go +++ b/openapi/security_scheme.go @@ -64,17 +64,18 @@ func mapOAuth2SchemeType(name string, scheme *openapi3.SecuritySchemeRef, securi } var cfg *auth.OAuthConfig - if scheme.Value.Flows.AuthorizationCode != nil { + switch { + case scheme.Value.Flows.AuthorizationCode != nil: cfg = &auth.OAuthConfig{ TokenURL: scheme.Value.Flows.AuthorizationCode.TokenURL, RefreshURL: scheme.Value.Flows.AuthorizationCode.RefreshURL, } - } else if scheme.Value.Flows.Implicit != nil { + case scheme.Value.Flows.Implicit != nil: cfg = &auth.OAuthConfig{ TokenURL: scheme.Value.Flows.Implicit.TokenURL, RefreshURL: scheme.Value.Flows.Implicit.RefreshURL, } - } else if scheme.Value.Flows.ClientCredentials != nil { + case scheme.Value.Flows.ClientCredentials != nil: cfg = &auth.OAuthConfig{ TokenURL: scheme.Value.Flows.ClientCredentials.TokenURL, RefreshURL: scheme.Value.Flows.ClientCredentials.RefreshURL, diff --git a/report/vuln.go b/report/vuln.go index aa20ace..7d8368e 100644 --- a/report/vuln.go +++ b/report/vuln.go @@ -117,17 +117,18 @@ func (vr *VulnerabilityReport) String() string { } func (vr *VulnerabilityReport) SeverityLevelString() string { - if vr.IsCriticalRiskSeverity() { + switch { + case vr.IsCriticalRiskSeverity(): return "Critical" - } else if vr.IsHighRiskSeverity() { + case vr.IsHighRiskSeverity(): return "High" - } else if vr.IsMediumRiskSeverity() { + case vr.IsMediumRiskSeverity(): return "Medium" - } else if vr.IsLowRiskSeverity() { + case vr.IsLowRiskSeverity(): return "Low" - } else if vr.IsInfoRiskSeverity() { + case vr.IsInfoRiskSeverity(): return "Info" - } else { + default: return "None" } } diff --git a/scan/graphql/introspection_enabled/introspection_enabled.go b/scan/graphql/introspection_enabled/introspection_enabled.go index 2f344ba..e84d9e9 100644 --- a/scan/graphql/introspection_enabled/introspection_enabled.go +++ b/scan/graphql/introspection_enabled/introspection_enabled.go @@ -58,7 +58,11 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem if err != nil { return r, err } - newOperation := request.NewOperationFromRequest(newRequest) + newOperation, err := request.NewOperationFromRequest(newRequest) + if err != nil { + return r, err + } + newOperation.SetSecuritySchemes(securitySchemes) attempt, err := scan.ScanURL(newOperation, &securityScheme) if err != nil { @@ -75,7 +79,11 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem if err != nil { return r, err } - newOperation = request.NewOperationFromRequest(newRequest) + newOperation, err = request.NewOperationFromRequest(newRequest) + if err != nil { + return r, err + } + newOperation.SetSecuritySchemes(securitySchemes) attempt, err = scan.ScanURL(newOperation, &securityScheme) if err != nil {