From de2c6a36fa2065b58cac83602982219c72b1e704 Mon Sep 17 00:00:00 2001 From: Cosmin Cojocar Date: Wed, 15 Feb 2023 20:44:13 +0100 Subject: [PATCH] Extract the issue in its own package --- Makefile | 5 +- analyzer.go | 69 ++++++++++--------- cmd/gosec/main.go | 17 ++--- cmd/gosec/sort_issues.go | 6 +- cmd/gosec/sort_issues_test.go | 20 +++--- cmd/tlsconfig/header_template.go | 1 + cmd/tlsconfig/rule_template.go | 2 +- issue.go => issue/issue.go | 16 +++-- issue_test.go => issue/issue_test.go | 12 ++-- report.go | 8 ++- report/formatter.go | 5 +- report/formatter_test.go | 99 ++++++++++++++-------------- report/junit/formatter.go | 3 +- report/sarif/formatter.go | 45 ++++++------- report/sarif/sarif_test.go | 39 +++++------ report/sonar/formatter.go | 5 +- report/sonar/sonar_test.go | 9 +-- report/text/writer.go | 11 ++-- rule.go | 4 +- rule_test.go | 11 ++-- rules/archive.go | 13 ++-- rules/bad_defer.go | 13 ++-- rules/bind.go | 17 ++--- rules/blocklist.go | 13 ++-- rules/decompression-bomb.go | 13 ++-- rules/directory-traversal.go | 15 +++-- rules/errors.go | 15 +++-- rules/fileperms.go | 25 +++---- rules/hardcoded_credentials.go | 23 +++---- rules/http_serve.go | 13 ++-- rules/implicit_aliasing.go | 13 ++-- rules/integer_overflow.go | 13 ++-- rules/math_big_rat.go | 15 +++-- rules/pprof.go | 13 ++-- rules/rand.go | 13 ++-- rules/readfile.go | 17 ++--- rules/rsa.go | 13 ++-- rules/slowloris.go | 13 ++-- rules/sql.go | 29 ++++---- rules/ssh.go | 13 ++-- rules/ssrf.go | 13 ++-- rules/subproc.go | 15 +++-- rules/tempfiles.go | 17 ++--- rules/templates.go | 13 ++-- rules/tls.go | 27 ++++---- rules/tls_config.go | 7 +- rules/unsafe.go | 13 ++-- rules/weakcrypto.go | 13 ++-- 48 files changed, 439 insertions(+), 378 deletions(-) rename issue.go => issue/issue.go (93%) rename issue_test.go => issue/issue_test.go (91%) diff --git a/Makefile b/Makefile index 093c8a9..09303d1 100644 --- a/Makefile +++ b/Makefile @@ -87,4 +87,7 @@ image-push: image docker push $(IMAGE_REPO)/$(BIN):$(GIT_TAG) docker push $(IMAGE_REPO)/$(BIN):latest -.PHONY: test build clean release image image-push +tlsconfig: + go generate ./... + +.PHONY: test build clean release image image-push tlsconfig diff --git a/analyzer.go b/analyzer.go index 64257e0..0cf24ee 100644 --- a/analyzer.go +++ b/analyzer.go @@ -32,6 +32,7 @@ import ( "sync" "github.com/securego/gosec/v2/analyzers" + "github.com/securego/gosec/v2/issue" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/packages" @@ -68,10 +69,21 @@ type Context struct { Root *ast.File Imports *ImportTracker Config Config - Ignores []map[string][]SuppressionInfo + Ignores []map[string][]issue.SuppressionInfo PassedValues map[string]interface{} } +// getFileAtNodePos returns the file at the node position in the file set available in the context. +func (ctx *Context) GetFileAtNodePos(node ast.Node) *token.File { + return ctx.FileSet.File(node.Pos()) +} + +// NewIssue creates a new issue +func (ctx *Context) NewIssue(node ast.Node, ruleID, desc string, + severity, confidence issue.Score) *issue.Issue { + return issue.New(ctx.GetFileAtNodePos(node), node, ruleID, desc, severity, confidence) +} + // Metrics used when reporting information about a scanning run. type Metrics struct { NumFiles int `json:"files"` @@ -88,7 +100,7 @@ type Analyzer struct { context *Context config Config logger *log.Logger - issues []*Issue + issues []*issue.Issue stats *Metrics errors map[string][]Error // keys are file paths; values are the golang errors in those files tests bool @@ -99,13 +111,6 @@ type Analyzer struct { analyzerList []*analysis.Analyzer } -// SuppressionInfo object is to record the kind and the justification that used -// to suppress violations. -type SuppressionInfo struct { - Kind string `json:"kind"` - Justification string `json:"justification"` -} - // NewAnalyzer builds a new analyzer. func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, trackSuppressions bool, concurrency int, logger *log.Logger) *Analyzer { ignoreNoSec := false @@ -126,7 +131,7 @@ func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, trackSuppressio context: &Context{}, config: conf, logger: logger, - issues: make([]*Issue, 0, 16), + issues: make([]*issue.Issue, 0, 16), stats: &Metrics{}, errors: make(map[string][]Error), tests: tests, @@ -371,8 +376,8 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { continue } if result != nil { - if issue, ok := result.(*analyzers.Issue); ok { - gosec.updateIssues(toGosecIssue(issue), false, []SuppressionInfo{}) + if aissue, ok := result.(*analyzers.Issue); ok { + gosec.updateIssues(toGosecIssue(aissue), false, []issue.SuppressionInfo{}) } } } @@ -439,7 +444,7 @@ func (gosec *Analyzer) AppendError(file string, err error) { } // ignore a node (and sub-tree) if it is tagged with a nosec tag comment -func (gosec *Analyzer) ignore(n ast.Node) map[string]SuppressionInfo { +func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo { if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec { // Checks if an alternative for #nosec is set and, if not, uses the default. @@ -476,13 +481,13 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]SuppressionInfo { re := regexp.MustCompile(`(G\d{3})`) matches := re.FindAllStringSubmatch(directive, -1) - suppression := SuppressionInfo{ + suppression := issue.SuppressionInfo{ Kind: "inSource", Justification: justification, } // Find the rule IDs to ignore. - ignores := make(map[string]SuppressionInfo) + ignores := make(map[string]issue.SuppressionInfo) for _, v := range matches { ignores[v[1]] = suppression } @@ -525,7 +530,7 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { return gosec } -func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]SuppressionInfo, bool) { +func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]issue.SuppressionInfo, bool) { if n == nil { if len(gosec.context.Ignores) > 0 { gosec.context.Ignores = gosec.context.Ignores[1:] @@ -536,7 +541,7 @@ func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]SuppressionI ignoredRules := gosec.ignore(n) // Now create the union of exclusions. - ignores := map[string][]SuppressionInfo{} + ignores := map[string][]issue.SuppressionInfo{} if len(gosec.context.Ignores) > 0 { for k, v := range gosec.context.Ignores[0] { ignores[k] = v @@ -548,12 +553,12 @@ func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]SuppressionI } // Push the new set onto the stack. - gosec.context.Ignores = append([]map[string][]SuppressionInfo{ignores}, gosec.context.Ignores...) + gosec.context.Ignores = append([]map[string][]issue.SuppressionInfo{ignores}, gosec.context.Ignores...) return ignores, true } -func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]SuppressionInfo) ([]SuppressionInfo, bool) { +func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.SuppressionInfo) ([]issue.SuppressionInfo, bool) { // Check if all rules are ignored. generalSuppressions, generalIgnored := ignores[aliasOfAllRules] // Check if the specific rule is ignored @@ -565,7 +570,7 @@ func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]Suppre // Track external suppressions. if gosec.ruleset.IsRuleSuppressed(id) { ignored = true - suppressions = append(suppressions, SuppressionInfo{ + suppressions = append(suppressions, issue.SuppressionInfo{ Kind: "external", Justification: externalSuppressionJustification, }) @@ -573,7 +578,7 @@ func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]Suppre return suppressions, ignored } -func (gosec *Analyzer) updateIssues(issue *Issue, ignored bool, suppressions []SuppressionInfo) { +func (gosec *Analyzer) updateIssues(issue *issue.Issue, ignored bool, suppressions []issue.SuppressionInfo) { if issue != nil { if gosec.showIgnored { issue.NoSec = ignored @@ -590,27 +595,27 @@ func (gosec *Analyzer) updateIssues(issue *Issue, ignored bool, suppressions []S } } -func toGosecIssue(issue *analyzers.Issue) *Issue { - return &Issue{ - File: issue.File, - Line: issue.Line, - Col: issue.Col, - RuleID: issue.AnalyzerID, - What: issue.What, - Confidence: Score(issue.Confidence), - Severity: Score(issue.Severity), +func toGosecIssue(aissue *analyzers.Issue) *issue.Issue { + return &issue.Issue{ + File: aissue.File, + Line: aissue.Line, + Col: aissue.Col, + RuleID: aissue.AnalyzerID, + What: aissue.What, + Confidence: issue.Score(aissue.Confidence), + Severity: issue.Score(aissue.Severity), } } // Report returns the current issues discovered and the metrics about the scan -func (gosec *Analyzer) Report() ([]*Issue, *Metrics, map[string][]Error) { +func (gosec *Analyzer) Report() ([]*issue.Issue, *Metrics, map[string][]Error) { return gosec.issues, gosec.stats, gosec.errors } // Reset clears state such as context, issues and metrics from the configured analyzer func (gosec *Analyzer) Reset() { gosec.context = &Context{} - gosec.issues = make([]*Issue, 0, 16) + gosec.issues = make([]*issue.Issue, 0, 16) gosec.stats = &Metrics{} gosec.ruleset = NewRuleSet() } diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index 1a3864b..4884103 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -26,6 +26,7 @@ import ( "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/cmd/vflag" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report" "github.com/securego/gosec/v2/rules" ) @@ -265,22 +266,22 @@ func saveReport(filename, format string, rootPaths []string, reportInfo *gosec.R return nil } -func convertToScore(value string) (gosec.Score, error) { +func convertToScore(value string) (issue.Score, error) { value = strings.ToLower(value) switch value { case "low": - return gosec.Low, nil + return issue.Low, nil case "medium": - return gosec.Medium, nil + return issue.Medium, nil case "high": - return gosec.High, nil + return issue.High, nil default: - return gosec.Low, fmt.Errorf("provided value '%s' not valid. Valid options: low, medium, high", value) + return issue.Low, fmt.Errorf("provided value '%s' not valid. Valid options: low, medium, high", value) } } -func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec.Score) ([]*gosec.Issue, int) { - result := make([]*gosec.Issue, 0) +func filterIssues(issues []*issue.Issue, severity issue.Score, confidence issue.Score) ([]*issue.Issue, int) { + result := make([]*issue.Issue, 0) trueIssues := 0 for _, issue := range issues { if issue.Severity >= severity && issue.Confidence >= confidence { @@ -293,7 +294,7 @@ func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec. return result, trueIssues } -func exit(issues []*gosec.Issue, errors map[string][]gosec.Error, noFail bool) { +func exit(issues []*issue.Issue, errors map[string][]gosec.Error, noFail bool) { nsi := 0 for _, issue := range issues { if len(issue.Suppressions) == 0 { diff --git a/cmd/gosec/sort_issues.go b/cmd/gosec/sort_issues.go index 3d661e2..04c1d3d 100644 --- a/cmd/gosec/sort_issues.go +++ b/cmd/gosec/sort_issues.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) // handle ranges @@ -14,7 +14,7 @@ func extractLineNumber(s string) int { return lineNumber } -type sortBySeverity []*gosec.Issue +type sortBySeverity []*issue.Issue func (s sortBySeverity) Len() int { return len(s) } @@ -34,6 +34,6 @@ func (s sortBySeverity) Less(i, j int) bool { func (s sortBySeverity) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // sortIssues sorts the issues by severity in descending order -func sortIssues(issues []*gosec.Issue) { +func sortIssues(issues []*issue.Issue) { sort.Sort(sortBySeverity(issues)) } diff --git a/cmd/gosec/sort_issues_test.go b/cmd/gosec/sort_issues_test.go index e92efe9..0777e62 100644 --- a/cmd/gosec/sort_issues_test.go +++ b/cmd/gosec/sort_issues_test.go @@ -5,22 +5,22 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) -var defaultIssue = gosec.Issue{ +var defaultIssue = issue.Issue{ File: "/home/src/project/test.go", Line: "1", Col: "1", RuleID: "ruleID", What: "test", - Confidence: gosec.High, - Severity: gosec.High, + Confidence: issue.High, + Severity: issue.High, Code: "1: testcode", - Cwe: gosec.GetCweByRule("G101"), + Cwe: issue.GetCweByRule("G101"), } -func createIssue() gosec.Issue { +func createIssue() issue.Issue { return defaultIssue } @@ -29,8 +29,8 @@ func TestRules(t *testing.T) { RunSpecs(t, "Sort issues Suite") } -func firstIsGreater(less, greater *gosec.Issue) { - slice := []*gosec.Issue{less, greater} +func firstIsGreater(less, greater *issue.Issue) { + slice := []*issue.Issue{less, greater} sortIssues(slice) @@ -40,9 +40,9 @@ func firstIsGreater(less, greater *gosec.Issue) { var _ = Describe("Sorting by Severity", func() { It("sorts by severity", func() { less := createIssue() - less.Severity = gosec.Low + less.Severity = issue.Low greater := createIssue() - less.Severity = gosec.High + less.Severity = issue.High firstIsGreater(&less, &greater) }) diff --git a/cmd/tlsconfig/header_template.go b/cmd/tlsconfig/header_template.go index c5ca67e..5e12498 100644 --- a/cmd/tlsconfig/header_template.go +++ b/cmd/tlsconfig/header_template.go @@ -9,5 +9,6 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) `)) diff --git a/cmd/tlsconfig/rule_template.go b/cmd/tlsconfig/rule_template.go index 0c65560..eee263d 100644 --- a/cmd/tlsconfig/rule_template.go +++ b/cmd/tlsconfig/rule_template.go @@ -7,7 +7,7 @@ var generatedRuleTmpl = template.Must(template.New("generated").Parse(` // DO NOT EDIT - generated by tlsconfig tool func New{{.Name}}TLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gosec.MetaData{ID: id}, + MetaData: issue.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: {{ .MinVersion }}, MaxVersion: {{ .MaxVersion }}, diff --git a/issue.go b/issue/issue.go similarity index 93% rename from issue.go rename to issue/issue.go index d8faf4b..75d2a7d 100644 --- a/issue.go +++ b/issue/issue.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gosec +package issue import ( "bufio" @@ -105,8 +105,15 @@ type Issue struct { Suppressions []SuppressionInfo `json:"suppressions"` // Suppression info of the issue } +// SuppressionInfo object is to record the kind and the justification that used +// to suppress violations. +type SuppressionInfo struct { + Kind string `json:"kind"` + Justification string `json:"justification"` +} + // FileLocation point out the file path and line number in file -func (i Issue) FileLocation() string { +func (i *Issue) FileLocation() string { return fmt.Sprintf("%s:%s", i.File, i.Line) } @@ -171,9 +178,8 @@ func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 { return e + SnippetOffset } -// NewIssue creates a new Issue -func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, confidence Score) *Issue { - fobj := ctx.FileSet.File(node.Pos()) +// New creates a new Issue +func New(fobj *token.File, node ast.Node, ruleID, desc string, severity, confidence Score) *Issue { name := fobj.Name() start, end := fobj.Line(node.Pos()), fobj.Line(node.End()) line := strconv.Itoa(start) diff --git a/issue_test.go b/issue/issue_test.go similarity index 91% rename from issue_test.go rename to issue/issue_test.go index eae7715..d10fa60 100644 --- a/issue_test.go +++ b/issue/issue_test.go @@ -1,4 +1,4 @@ -package gosec_test +package issue_test import ( "go/ast" @@ -6,6 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/rules" "github.com/securego/gosec/v2/testutils" ) @@ -36,7 +37,8 @@ var _ = Describe("Issue", func() { ast.Walk(v, ctx.Root) Expect(target).ShouldNot(BeNil()) - issue := gosec.NewIssue(ctx, target, "TEST", "", gosec.High, gosec.High) + fobj := ctx.GetFileAtNodePos(target) + issue := issue.New(fobj, target, "TEST", "", issue.High, issue.High) Expect(issue).ShouldNot(BeNil()) Expect(issue.Code).Should(MatchRegexp(`"bar"`)) Expect(issue.Line).Should(Equal("2")) @@ -79,10 +81,10 @@ var _ = Describe("Issue", func() { // Use hardcodeded rule to check assignment cfg := gosec.NewConfig() rule, _ := rules.NewHardcodedCredentials("TEST", cfg) - issue, err := rule.Match(target, ctx) + foundIssue, err := rule.Match(target, ctx) Expect(err).ShouldNot(HaveOccurred()) - Expect(issue).ShouldNot(BeNil()) - Expect(issue.FileLocation()).Should(MatchRegexp("foo.go:5")) + Expect(foundIssue).ShouldNot(BeNil()) + Expect(foundIssue.FileLocation()).Should(MatchRegexp("foo.go:5")) }) It("should provide accurate line and file information", func() { diff --git a/report.go b/report.go index 96b1466..4fdeea5 100644 --- a/report.go +++ b/report.go @@ -1,15 +1,19 @@ package gosec +import ( + "github.com/securego/gosec/v2/issue" +) + // ReportInfo this is report information type ReportInfo struct { Errors map[string][]Error `json:"Golang errors"` - Issues []*Issue + Issues []*issue.Issue Stats *Metrics GosecVersion string } // NewReportInfo instantiate a ReportInfo -func NewReportInfo(issues []*Issue, metrics *Metrics, errors map[string][]Error) *ReportInfo { +func NewReportInfo(issues []*issue.Issue, metrics *Metrics, errors map[string][]Error) *ReportInfo { return &ReportInfo{ Errors: errors, Issues: issues, diff --git a/report/formatter.go b/report/formatter.go index 14c90b9..3e83dc6 100644 --- a/report/formatter.go +++ b/report/formatter.go @@ -18,6 +18,7 @@ import ( "io" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report/csv" "github.com/securego/gosec/v2/report/golint" "github.com/securego/gosec/v2/report/html" @@ -81,8 +82,8 @@ func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []stri return err } -func filterOutSuppressedIssues(issues []*gosec.Issue) []*gosec.Issue { - nonSuppressedIssues := []*gosec.Issue{} +func filterOutSuppressedIssues(issues []*issue.Issue) []*issue.Issue { + nonSuppressedIssues := []*issue.Issue{} for _, issue := range issues { if len(issue.Suppressions) == 0 { nonSuppressedIssues = append(nonSuppressedIssues, issue) diff --git a/report/formatter_test.go b/report/formatter_test.go index ba414db..36c337c 100644 --- a/report/formatter_test.go +++ b/report/formatter_test.go @@ -10,39 +10,40 @@ import ( . "github.com/onsi/gomega" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/cwe" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report/junit" "github.com/securego/gosec/v2/report/sonar" "gopkg.in/yaml.v3" ) -func createIssueWithFileWhat(file, what string) *gosec.Issue { - issue := createIssue("i1", gosec.GetCweByRule("G101")) +func createIssueWithFileWhat(file, what string) *issue.Issue { + issue := createIssue("i1", issue.GetCweByRule("G101")) issue.File = file issue.What = what return &issue } -func createIssue(ruleID string, weakness *cwe.Weakness) gosec.Issue { - return gosec.Issue{ +func createIssue(ruleID string, weakness *cwe.Weakness) issue.Issue { + return issue.Issue{ File: "/home/src/project/test.go", Line: "1", Col: "1", RuleID: ruleID, What: "test", - Confidence: gosec.High, - Severity: gosec.High, + Confidence: issue.High, + Severity: issue.High, Code: "1: testcode", Cwe: weakness, } } func createReportInfo(rule string, weakness *cwe.Weakness) gosec.ReportInfo { - issue := createIssue(rule, weakness) + newissue := createIssue(rule, weakness) metrics := gosec.Metrics{} return gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ - &issue, + Issues: []*issue.Issue{ + &newissue, }, Stats: &metrics, } @@ -62,7 +63,7 @@ var _ = Describe("Formatter", func() { It("it should parse the report info", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -110,7 +111,7 @@ var _ = Describe("Formatter", func() { It("it should parse the report info with files in subfolders", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -157,7 +158,7 @@ var _ = Describe("Formatter", func() { It("it should not parse the report info for files from other projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -189,7 +190,7 @@ var _ = Describe("Formatter", func() { It("it should parse the report info for multiple projects projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -261,7 +262,7 @@ var _ = Describe("Formatter", func() { Context("When using junit", func() { It("preserves order of issues", func() { - issues := []*gosec.Issue{createIssueWithFileWhat("i1", "1"), createIssueWithFileWhat("i2", "2"), createIssueWithFileWhat("i3", "1")} + issues := []*issue.Issue{createIssueWithFileWhat("i1", "1"), createIssueWithFileWhat("i2", "2"), createIssueWithFileWhat("i3", "1")} junitReport := junit.GenerateReport(&gosec.ReportInfo{Issues: issues}) @@ -285,12 +286,12 @@ var _ = Describe("Formatter", func() { It("csv formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "csv", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,1: testcode,CWE-%s\n" @@ -300,12 +301,12 @@ var _ = Describe("Formatter", func() { }) It("xml formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, error).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, error).WithVersion("v2.7.0") err := CreateReport(buf, "xml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > 1: testcode\n\n\n\nSummary:\n Gosec : v2.7.0\n Files : 0\n Lines : 0\n Nosec : 0\n Issues : 0\n\n" @@ -315,8 +316,8 @@ var _ = Describe("Formatter", func() { }) It("json formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) @@ -326,7 +327,7 @@ var _ = Describe("Formatter", func() { err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "json", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) @@ -336,8 +337,8 @@ var _ = Describe("Formatter", func() { }) It("html formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) @@ -347,7 +348,7 @@ var _ = Describe("Formatter", func() { err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "html", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) @@ -357,8 +358,8 @@ var _ = Describe("Formatter", func() { }) It("yaml formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) @@ -368,7 +369,7 @@ var _ = Describe("Formatter", func() { err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "yaml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) @@ -378,8 +379,8 @@ var _ = Describe("Formatter", func() { }) It("junit-xml formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) @@ -389,7 +390,7 @@ var _ = Describe("Formatter", func() { err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "junit-xml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - test (Confidence: 2, Severity: 2, CWE: %s)", cwe.ID)) @@ -399,8 +400,8 @@ var _ = Describe("Formatter", func() { }) It("text formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) @@ -410,7 +411,7 @@ var _ = Describe("Formatter", func() { err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "text", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)", rule, cwe.ID)) @@ -420,11 +421,11 @@ var _ = Describe("Formatter", func() { }) It("sonarqube formatted report shouldn't contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "sonarqube", false, []string{"/home/src/project"}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) @@ -441,12 +442,12 @@ var _ = Describe("Formatter", func() { }) It("golint formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "golint", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "/home/src/project/test.go:1:1: [CWE-%s] test (Rule:%s, Severity:HIGH, Confidence:HIGH)\n" @@ -456,12 +457,12 @@ var _ = Describe("Formatter", func() { }) It("sarif formatted report should contain the CWE mapping", func() { for _, rule := range grules { - cwe := gosec.GetCweByRule(rule) - issue := createIssue(rule, cwe) + cwe := issue.GetCweByRule(rule) + newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, error).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error).WithVersion("v2.7.0") err := CreateReport(buf, "sarif", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) @@ -490,8 +491,8 @@ var _ = Describe("Formatter", func() { Context("When converting suppressed issues", func() { ruleID := "G101" - cwe := gosec.GetCweByRule(ruleID) - suppressions := []gosec.SuppressionInfo{ + cwe := issue.GetCweByRule(ruleID) + suppressions := []issue.SuppressionInfo{ { Kind: "kind", Justification: "justification", @@ -502,7 +503,7 @@ var _ = Describe("Formatter", func() { It("text formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&suppressedIssue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "text", false, []string{}, reportInfo) @@ -514,7 +515,7 @@ var _ = Describe("Formatter", func() { It("sarif formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&suppressedIssue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "sarif", false, []string{}, reportInfo) @@ -526,7 +527,7 @@ var _ = Describe("Formatter", func() { It("json formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&suppressedIssue}, &gosec.Metrics{}, error) + reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "json", false, []string{}, reportInfo) diff --git a/report/junit/formatter.go b/report/junit/formatter.go index d14def6..62d6f02 100644 --- a/report/junit/formatter.go +++ b/report/junit/formatter.go @@ -5,9 +5,10 @@ import ( "strconv" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) -func generatePlaintext(issue *gosec.Issue) string { +func generatePlaintext(issue *issue.Issue) string { cweID := "CWE" if issue.Cwe != nil { cweID = issue.Cwe.ID diff --git a/report/sarif/formatter.go b/report/sarif/formatter.go index 16198dd..5c029cc 100644 --- a/report/sarif/formatter.go +++ b/report/sarif/formatter.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/cwe" + "github.com/securego/gosec/v2/issue" ) // GenerateReport Convert a gosec report to a Sarif Report @@ -72,28 +73,28 @@ func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error) } // parseSarifRule return SARIF rule field struct -func parseSarifRule(issue *gosec.Issue) *ReportingDescriptor { - cwe := gosec.GetCweByRule(issue.RuleID) - name := issue.RuleID +func parseSarifRule(i *issue.Issue) *ReportingDescriptor { + cwe := issue.GetCweByRule(i.RuleID) + name := i.RuleID if cwe != nil { name = cwe.Name } return &ReportingDescriptor{ - ID: issue.RuleID, + ID: i.RuleID, Name: name, - ShortDescription: NewMultiformatMessageString(issue.What), - FullDescription: NewMultiformatMessageString(issue.What), + ShortDescription: NewMultiformatMessageString(i.What), + FullDescription: NewMultiformatMessageString(i.What), Help: NewMultiformatMessageString(fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\n", - issue.What, issue.Severity.String(), issue.Confidence.String())), + i.What, i.Severity.String(), i.Confidence.String())), Properties: &PropertyBag{ - "tags": []string{"security", issue.Severity.String()}, - "precision": strings.ToLower(issue.Confidence.String()), + "tags": []string{"security", i.Severity.String()}, + "precision": strings.ToLower(i.Confidence.String()), }, DefaultConfiguration: &ReportingConfiguration{ - Level: getSarifLevel(issue.Severity.String()), + Level: getSarifLevel(i.Severity.String()), }, Relationships: []*ReportingDescriptorRelationship{ - buildSarifReportingDescriptorRelationship(issue.Cwe), + buildSarifReportingDescriptorRelationship(i.Cwe), }, } } @@ -157,27 +158,27 @@ func uuid3(value string) string { } // parseSarifLocation return SARIF location struct -func parseSarifLocation(issue *gosec.Issue, rootPaths []string) (*Location, error) { - region, err := parseSarifRegion(issue) +func parseSarifLocation(i *issue.Issue, rootPaths []string) (*Location, error) { + region, err := parseSarifRegion(i) if err != nil { return nil, err } - artifactLocation := parseSarifArtifactLocation(issue, rootPaths) + artifactLocation := parseSarifArtifactLocation(i, rootPaths) return NewLocation(NewPhysicalLocation(artifactLocation, region)), nil } -func parseSarifArtifactLocation(issue *gosec.Issue, rootPaths []string) *ArtifactLocation { +func parseSarifArtifactLocation(i *issue.Issue, rootPaths []string) *ArtifactLocation { var filePath string for _, rootPath := range rootPaths { - if strings.HasPrefix(issue.File, rootPath) { - filePath = strings.Replace(issue.File, rootPath+"/", "", 1) + if strings.HasPrefix(i.File, rootPath) { + filePath = strings.Replace(i.File, rootPath+"/", "", 1) } } return NewArtifactLocation(filePath) } -func parseSarifRegion(issue *gosec.Issue) (*Region, error) { - lines := strings.Split(issue.Line, "-") +func parseSarifRegion(i *issue.Issue) (*Region, error) { + lines := strings.Split(i.Line, "-") startLine, err := strconv.Atoi(lines[0]) if err != nil { return nil, err @@ -189,13 +190,13 @@ func parseSarifRegion(issue *gosec.Issue) (*Region, error) { return nil, err } } - col, err := strconv.Atoi(issue.Col) + col, err := strconv.Atoi(i.Col) if err != nil { return nil, err } var code string line := startLine - codeLines := strings.Split(issue.Code, "\n") + codeLines := strings.Split(i.Code, "\n") for _, codeLine := range codeLines { lineStart := fmt.Sprintf("%d:", line) if strings.HasPrefix(codeLine, lineStart) { @@ -227,7 +228,7 @@ func getSarifLevel(s string) Level { } } -func buildSarifSuppressions(suppressions []gosec.SuppressionInfo) []*Suppression { +func buildSarifSuppressions(suppressions []issue.SuppressionInfo) []*Suppression { var sarifSuppressionList []*Suppression for _, s := range suppressions { sarifSuppressionList = append(sarifSuppressionList, NewSuppression(s.Kind, s.Justification)) diff --git a/report/sarif/sarif_test.go b/report/sarif/sarif_test.go index f70d828..0fa40a7 100644 --- a/report/sarif/sarif_test.go +++ b/report/sarif/sarif_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report/sarif" ) @@ -16,7 +17,7 @@ var _ = Describe("Sarif Formatter", func() { Context("when converting to Sarif issues", func() { It("sarif formatted report should contain the result", func() { buf := new(bytes.Buffer) - reportInfo := gosec.NewReportInfo([]*gosec.Issue{}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") err := sarif.WriteReport(buf, reportInfo, []string{}) result := buf.String() Expect(err).ShouldNot(HaveOccurred()) @@ -25,18 +26,18 @@ var _ = Describe("Sarif Formatter", func() { It("sarif formatted report should contain the suppressed results", func() { ruleID := "G101" - cwe := gosec.GetCweByRule(ruleID) - suppressedIssue := gosec.Issue{ + cwe := issue.GetCweByRule(ruleID) + suppressedIssue := issue.Issue{ File: "/home/src/project/test.go", Line: "1", Col: "1", RuleID: ruleID, What: "test", - Confidence: gosec.High, - Severity: gosec.High, + Confidence: issue.High, + Severity: issue.High, Code: "1: testcode", Cwe: cwe, - Suppressions: []gosec.SuppressionInfo{ + Suppressions: []issue.SuppressionInfo{ { Kind: "kind", Justification: "justification", @@ -44,7 +45,7 @@ var _ = Describe("Sarif Formatter", func() { }, } - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&suppressedIssue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") buf := new(bytes.Buffer) err := sarif.WriteReport(buf, reportInfo, []string{}) result := buf.String() @@ -58,54 +59,54 @@ var _ = Describe("Sarif Formatter", func() { }) It("sarif formatted report should contain the formatted one line code snippet", func() { ruleID := "G101" - cwe := gosec.GetCweByRule(ruleID) + cwe := issue.GetCweByRule(ruleID) code := "68: \t\t}\n69: \t\tvar data = template.HTML(v.TmplFile)\n70: \t\tisTmpl := true\n" expectedCode := "var data = template.HTML(v.TmplFile)" - issue := gosec.Issue{ + newissue := issue.Issue{ File: "/home/src/project/test.go", Line: "69", Col: "14", RuleID: ruleID, What: "test", - Confidence: gosec.High, - Severity: gosec.High, + Confidence: issue.High, + Severity: issue.High, Code: code, Cwe: cwe, - Suppressions: []gosec.SuppressionInfo{ + Suppressions: []issue.SuppressionInfo{ { Kind: "kind", Justification: "justification", }, }, } - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") sarifReport, err := sarif.GenerateReport([]string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) Expect(sarifReport.Runs[0].Results[0].Locations[0].PhysicalLocation.Region.Snippet.Text).Should(Equal(expectedCode)) }) It("sarif formatted report should contain the formatted multiple line code snippet", func() { ruleID := "G101" - cwe := gosec.GetCweByRule(ruleID) + cwe := issue.GetCweByRule(ruleID) code := "68: }\n69: var data = template.HTML(v.TmplFile)\n70: isTmpl := true\n" expectedCode := "var data = template.HTML(v.TmplFile)\nisTmpl := true\n" - issue := gosec.Issue{ + newissue := issue.Issue{ File: "/home/src/project/test.go", Line: "69-70", Col: "14", RuleID: ruleID, What: "test", - Confidence: gosec.High, - Severity: gosec.High, + Confidence: issue.High, + Severity: issue.High, Code: code, Cwe: cwe, - Suppressions: []gosec.SuppressionInfo{ + Suppressions: []issue.SuppressionInfo{ { Kind: "kind", Justification: "justification", }, }, } - reportInfo := gosec.NewReportInfo([]*gosec.Issue{&issue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") + reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0") sarifReport, err := sarif.GenerateReport([]string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) Expect(sarifReport.Runs[0].Results[0].Locations[0].PhysicalLocation.Region.Snippet.Text).Should(Equal(expectedCode)) diff --git a/report/sonar/formatter.go b/report/sonar/formatter.go index 5d1e59f..d635d9d 100644 --- a/report/sonar/formatter.go +++ b/report/sonar/formatter.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) const ( @@ -36,7 +37,7 @@ func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error) return si, nil } -func parseFilePath(issue *gosec.Issue, rootPaths []string) string { +func parseFilePath(issue *issue.Issue, rootPaths []string) string { var sonarFilePath string for _, rootPath := range rootPaths { if strings.HasPrefix(issue.File, rootPath) { @@ -46,7 +47,7 @@ func parseFilePath(issue *gosec.Issue, rootPaths []string) string { return sonarFilePath } -func parseTextRange(issue *gosec.Issue) (*TextRange, error) { +func parseTextRange(issue *issue.Issue) (*TextRange, error) { lines := strings.Split(issue.Line, "-") startLine, err := strconv.Atoi(lines[0]) if err != nil { diff --git a/report/sonar/sonar_test.go b/report/sonar/sonar_test.go index 9afdbf5..13a0fb1 100644 --- a/report/sonar/sonar_test.go +++ b/report/sonar/sonar_test.go @@ -4,6 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report/sonar" ) @@ -14,7 +15,7 @@ var _ = Describe("Sonar Formatter", func() { It("it should parse the report info", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -62,7 +63,7 @@ var _ = Describe("Sonar Formatter", func() { It("it should parse the report info with files in subfolders", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -109,7 +110,7 @@ var _ = Describe("Sonar Formatter", func() { It("it should not parse the report info for files from other projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, @@ -141,7 +142,7 @@ var _ = Describe("Sonar Formatter", func() { It("it should parse the report info for multiple projects projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, - Issues: []*gosec.Issue{ + Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, diff --git a/report/text/writer.go b/report/text/writer.go index 2ea8d7c..f7d50a3 100644 --- a/report/text/writer.go +++ b/report/text/writer.go @@ -12,6 +12,7 @@ import ( "github.com/gookit/color" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) var ( @@ -49,7 +50,7 @@ func plainTextFuncMap(enableColor bool) template.FuncMap { // by default those functions return the given content untouched return template.FuncMap{ - "highlight": func(t string, s gosec.Score, ignored bool) string { + "highlight": func(t string, s issue.Score, ignored bool) string { return t }, "danger": fmt.Sprint, @@ -60,14 +61,14 @@ func plainTextFuncMap(enableColor bool) template.FuncMap { } // highlight returns content t colored based on Score -func highlight(t string, s gosec.Score, ignored bool) string { +func highlight(t string, s issue.Score, ignored bool) string { if ignored { return defaultTheme.Sprint(t) } switch s { - case gosec.High: + case issue.High: return errorTheme.Sprint(t) - case gosec.Medium: + case issue.Medium: return warningTheme.Sprint(t) default: return defaultTheme.Sprint(t) @@ -75,7 +76,7 @@ func highlight(t string, s gosec.Score, ignored bool) string { } // printCodeSnippet prints the code snippet from the issue by adding a marker to the affected line -func printCodeSnippet(issue *gosec.Issue) string { +func printCodeSnippet(issue *issue.Issue) string { start, end := parseLine(issue.Line) scanner := bufio.NewScanner(strings.NewReader(issue.Code)) var buf bytes.Buffer diff --git a/rule.go b/rule.go index c0429c4..5e973b6 100644 --- a/rule.go +++ b/rule.go @@ -15,12 +15,14 @@ package gosec import ( "go/ast" "reflect" + + "github.com/securego/gosec/v2/issue" ) // The Rule interface used by all rules supported by gosec. type Rule interface { ID() string - Match(ast.Node, *Context) (*Issue, error) + Match(ast.Node, *Context) (*issue.Issue, error) } // RuleBuilder is used to register a rule definition with the analyzer diff --git a/rule_test.go b/rule_test.go index e800986..1dfbd30 100644 --- a/rule_test.go +++ b/rule_test.go @@ -7,10 +7,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type mockrule struct { - issue *gosec.Issue + issue *issue.Issue err error callback func(n ast.Node, ctx *gosec.Context) bool } @@ -19,7 +20,7 @@ func (m *mockrule) ID() string { return "MOCK" } -func (m *mockrule) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (m *mockrule) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { if m.callback(n, ctx) { return m.issue, nil } @@ -42,9 +43,9 @@ var _ = Describe("Rule", func() { callback: func(n ast.Node, ctx *gosec.Context) bool { return false }, } dummyIssueRule = &mockrule{ - issue: &gosec.Issue{ - Severity: gosec.High, - Confidence: gosec.High, + issue: &issue.Issue{ + Severity: issue.High, + Confidence: issue.High, What: `Some explanation of the thing`, File: "main.go", Code: `#include int main(){ puts("hello world"); }`, diff --git a/rules/archive.go b/rules/archive.go index 92c7e44..509f818 100644 --- a/rules/archive.go +++ b/rules/archive.go @@ -5,10 +5,11 @@ import ( "go/types" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type archive struct { - gosec.MetaData + issue.MetaData calls gosec.CallList argTypes []string } @@ -18,7 +19,7 @@ func (a *archive) ID() string { } // Match inspects AST nodes to determine if the filepath.Joins uses any argument derived from type zip.File or tar.Header -func (a *archive) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (a *archive) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node := a.calls.ContainsPkgCallExpr(n, c, false); node != nil { for _, arg := range node.Args { var argType types.Type @@ -38,7 +39,7 @@ func (a *archive) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if argType != nil { for _, t := range a.argTypes { if argType.String() == t { - return gosec.NewIssue(c, n, a.ID(), a.What, a.Severity, a.Confidence), nil + return c.NewIssue(n, a.ID(), a.What, a.Severity, a.Confidence), nil } } } @@ -55,10 +56,10 @@ func NewArchive(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &archive{ calls: calls, argTypes: []string{"*archive/zip.File", "*archive/tar.Header"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "File traversal when extracting zip/tar archive", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/bad_defer.go b/rules/bad_defer.go index 141a4a9..d0c1805 100644 --- a/rules/bad_defer.go +++ b/rules/bad_defer.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type deferType struct { @@ -14,7 +15,7 @@ type deferType struct { } type badDefer struct { - gosec.MetaData + issue.MetaData types []deferType } @@ -35,12 +36,12 @@ func contains(methods []string, method string) bool { return false } -func (r *badDefer) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *badDefer) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if deferStmt, ok := n.(*ast.DeferStmt); ok { for _, deferTyp := range r.types { if typ, method, err := gosec.GetCallInfo(deferStmt.Call, c); err == nil { if normalize(typ) == deferTyp.typ && contains(deferTyp.methods, method) { - return gosec.NewIssue(c, n, r.ID(), fmt.Sprintf(r.What, method, typ), r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), fmt.Sprintf(r.What, method, typ), r.Severity, r.Confidence), nil } } } @@ -86,10 +87,10 @@ func NewDeferredClosing(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { methods: []string{"Close"}, }, }, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "Deferring unsafe method %q on type %q", }, }, []ast.Node{(*ast.DeferStmt)(nil)} diff --git a/rules/bind.go b/rules/bind.go index 8f6af06..7d64dcb 100644 --- a/rules/bind.go +++ b/rules/bind.go @@ -19,11 +19,12 @@ import ( "regexp" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) // Looks for net.Listen("0.0.0.0") or net.Listen(":8080") type bindsToAllNetworkInterfaces struct { - gosec.MetaData + issue.MetaData calls gosec.CallList pattern *regexp.Regexp } @@ -32,7 +33,7 @@ func (r *bindsToAllNetworkInterfaces) ID() string { return r.MetaData.ID } -func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { callExpr := r.calls.ContainsPkgCallExpr(n, c, false) if callExpr == nil { return nil, nil @@ -42,14 +43,14 @@ func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*gose if bl, ok := arg.(*ast.BasicLit); ok { if arg, err := gosec.GetString(bl); err == nil { if r.pattern.MatchString(arg) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } else if ident, ok := arg.(*ast.Ident); ok { values := gosec.GetIdentStringValues(ident) for _, value := range values { if r.pattern.MatchString(value) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -57,7 +58,7 @@ func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*gose values := gosec.GetCallStringArgsValues(callExpr.Args[0], c) for _, value := range values { if r.pattern.MatchString(value) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -73,10 +74,10 @@ func NewBindsToAllNetworkInterfaces(id string, conf gosec.Config) (gosec.Rule, [ return &bindsToAllNetworkInterfaces{ calls: calls, pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`), - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "Binds to all network interfaces", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/blocklist.go b/rules/blocklist.go index afd4ee5..003361c 100644 --- a/rules/blocklist.go +++ b/rules/blocklist.go @@ -19,10 +19,11 @@ import ( "strings" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type blocklistedImport struct { - gosec.MetaData + issue.MetaData Blocklisted map[string]string } @@ -36,10 +37,10 @@ func (r *blocklistedImport) ID() string { return r.MetaData.ID } -func (r *blocklistedImport) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *blocklistedImport) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node, ok := n.(*ast.ImportSpec); ok { if description, ok := r.Blocklisted[unquote(node.Path.Value)]; ok { - return gosec.NewIssue(c, node, r.ID(), description, r.Severity, r.Confidence), nil + return c.NewIssue(node, r.ID(), description, r.Severity, r.Confidence), nil } } return nil, nil @@ -49,10 +50,10 @@ func (r *blocklistedImport) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, e // Typically when a deprecated technology is being used. func NewBlocklistedImports(id string, conf gosec.Config, blocklist map[string]string) (gosec.Rule, []ast.Node) { return &blocklistedImport{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, }, Blocklisted: blocklist, }, []ast.Node{(*ast.ImportSpec)(nil)} diff --git a/rules/decompression-bomb.go b/rules/decompression-bomb.go index 02256fa..4c1ef1d 100644 --- a/rules/decompression-bomb.go +++ b/rules/decompression-bomb.go @@ -19,10 +19,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type decompressionBombCheck struct { - gosec.MetaData + issue.MetaData readerCalls gosec.CallList copyCalls gosec.CallList } @@ -40,7 +41,7 @@ func containsReaderCall(node ast.Node, ctx *gosec.Context, list gosec.CallList) return list.Contains(s, idt) } -func (d *decompressionBombCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (d *decompressionBombCheck) Match(node ast.Node, ctx *gosec.Context) (*issue.Issue, error) { var readerVarObj map[*ast.Object]struct{} // To check multiple lines, ctx.PassedValues is used to store temporary data. @@ -72,7 +73,7 @@ func (d *decompressionBombCheck) Match(node ast.Node, ctx *gosec.Context) (*gose if idt, ok := n.Args[1].(*ast.Ident); ok { if _, ok := readerVarObj[idt.Obj]; ok { // Detect io.Copy(x, r) - return gosec.NewIssue(ctx, n, d.ID(), d.What, d.Severity, d.Confidence), nil + return ctx.NewIssue(n, d.ID(), d.What, d.Severity, d.Confidence), nil } } } @@ -98,10 +99,10 @@ func NewDecompressionBombCheck(id string, conf gosec.Config) (gosec.Rule, []ast. copyCalls.Add("io", "CopyBuffer") return &decompressionBombCheck{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.Medium, + Severity: issue.Medium, + Confidence: issue.Medium, What: "Potential DoS vulnerability via decompression bomb", }, readerCalls: readerCalls, diff --git a/rules/directory-traversal.go b/rules/directory-traversal.go index c373427..77b85f9 100644 --- a/rules/directory-traversal.go +++ b/rules/directory-traversal.go @@ -5,18 +5,19 @@ import ( "regexp" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type traversal struct { pattern *regexp.Regexp - gosec.MetaData + issue.MetaData } func (r *traversal) ID() string { return r.MetaData.ID } -func (r *traversal) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *traversal) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.CallExpr: return r.matchCallExpr(node, ctx) @@ -24,14 +25,14 @@ func (r *traversal) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) return nil, nil } -func (r *traversal) matchCallExpr(assign *ast.CallExpr, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *traversal) matchCallExpr(assign *ast.CallExpr, ctx *gosec.Context) (*issue.Issue, error) { for _, i := range assign.Args { if basiclit, ok1 := i.(*ast.BasicLit); ok1 { if fun, ok2 := assign.Fun.(*ast.SelectorExpr); ok2 { if x, ok3 := fun.X.(*ast.Ident); ok3 { string := x.Name + "." + fun.Sel.Name + "(" + basiclit.Value + ")" if r.pattern.MatchString(string) { - return gosec.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(assign, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -54,11 +55,11 @@ func NewDirectoryTraversal(id string, conf gosec.Config) (gosec.Rule, []ast.Node return &traversal{ pattern: regexp.MustCompile(pattern), - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential directory traversal", - Confidence: gosec.Medium, - Severity: gosec.Medium, + Confidence: issue.Medium, + Severity: issue.Medium, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/errors.go b/rules/errors.go index 0838382..d31248c 100644 --- a/rules/errors.go +++ b/rules/errors.go @@ -19,10 +19,11 @@ import ( "go/types" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type noErrorCheck struct { - gosec.MetaData + issue.MetaData whitelist gosec.CallList } @@ -49,7 +50,7 @@ func returnsError(callExpr *ast.CallExpr, ctx *gosec.Context) int { return -1 } -func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch stmt := n.(type) { case *ast.AssignStmt: cfg := ctx.Config @@ -61,7 +62,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, erro return nil, nil } if id, ok := stmt.Lhs[pos].(*ast.Ident); ok && id.Name == "_" { - return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -70,7 +71,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, erro if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil { pos := returnsError(callExpr, ctx) if pos >= 0 { - return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -100,10 +101,10 @@ func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { } return &noErrorCheck{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Low, - Confidence: gosec.High, + Severity: issue.Low, + Confidence: issue.High, What: "Errors unhandled.", }, whitelist: whitelist, diff --git a/rules/fileperms.go b/rules/fileperms.go index e89b563..0376b6a 100644 --- a/rules/fileperms.go +++ b/rules/fileperms.go @@ -20,10 +20,11 @@ import ( "strconv" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type filePermissions struct { - gosec.MetaData + issue.MetaData mode int64 pkgs []string calls []string @@ -54,12 +55,12 @@ func modeIsSubset(subset int64, superset int64) bool { return (subset | superset) == superset } -func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { for _, pkg := range r.pkgs { if callexpr, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched { modeArg := callexpr.Args[len(callexpr.Args)-1] if mode, err := gosec.GetInt(modeArg); err == nil && !modeIsSubset(mode, r.mode) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -73,10 +74,10 @@ func NewWritePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode: mode, pkgs: []string{"io/ioutil", "os"}, calls: []string{"WriteFile"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: fmt.Sprintf("Expect WriteFile permissions to be %#o or less", mode), }, }, []ast.Node{(*ast.CallExpr)(nil)} @@ -90,10 +91,10 @@ func NewFilePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode: mode, pkgs: []string{"os"}, calls: []string{"OpenFile", "Chmod"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: fmt.Sprintf("Expect file permissions to be %#o or less", mode), }, }, []ast.Node{(*ast.CallExpr)(nil)} @@ -107,10 +108,10 @@ func NewMkdirPerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode: mode, pkgs: []string{"os"}, calls: []string{"Mkdir", "MkdirAll"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode), }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/hardcoded_credentials.go b/rules/hardcoded_credentials.go index b9e5756..d908e0b 100644 --- a/rules/hardcoded_credentials.go +++ b/rules/hardcoded_credentials.go @@ -22,10 +22,11 @@ import ( zxcvbn "github.com/nbutton23/zxcvbn-go" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type credentials struct { - gosec.MetaData + issue.MetaData pattern *regexp.Regexp entropyThreshold float64 perCharThreshold float64 @@ -53,7 +54,7 @@ func (r *credentials) isHighEntropyString(str string) bool { entropyPerChar >= r.perCharThreshold)) } -func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.AssignStmt: return r.matchAssign(node, ctx) @@ -65,14 +66,14 @@ func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error return nil, nil } -func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*issue.Issue, error) { for _, i := range assign.Lhs { if ident, ok := i.(*ast.Ident); ok { if r.pattern.MatchString(ident.Name) { for _, e := range assign.Rhs { if val, err := gosec.GetString(e); err == nil { if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) { - return gosec.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(assign, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -82,7 +83,7 @@ func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (* return nil, nil } -func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Context) (*issue.Issue, error) { for index, ident := range valueSpec.Names { if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil { // const foo, bar = "same value" @@ -91,7 +92,7 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Contex } if val, err := gosec.GetString(valueSpec.Values[index]); err == nil { if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) { - return gosec.NewIssue(ctx, valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -99,7 +100,7 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Contex return nil, nil } -func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.Context) (*issue.Issue, error) { if binaryExpr.Op == token.EQL || binaryExpr.Op == token.NEQ { ident, ok := binaryExpr.X.(*ast.Ident) if !ok { @@ -113,7 +114,7 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec. } if val, err := gosec.GetString(valueNode); err == nil { if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) { - return gosec.NewIssue(ctx, binaryExpr, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(binaryExpr, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -170,11 +171,11 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No perCharThreshold: perCharThreshold, ignoreEntropy: ignoreEntropy, truncate: truncateString, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential hardcoded credentials", - Confidence: gosec.Low, - Severity: gosec.High, + Confidence: issue.Low, + Severity: issue.High, }, }, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil)} } diff --git a/rules/http_serve.go b/rules/http_serve.go index e460b3a..b94b914 100644 --- a/rules/http_serve.go +++ b/rules/http_serve.go @@ -4,10 +4,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type httpServeWithoutTimeouts struct { - gosec.MetaData + issue.MetaData pkg string calls []string } @@ -16,9 +17,9 @@ func (r *httpServeWithoutTimeouts) ID() string { return r.MetaData.ID } -func (r *httpServeWithoutTimeouts) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { +func (r *httpServeWithoutTimeouts) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err error) { if _, matches := gosec.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } return nil, nil } @@ -28,11 +29,11 @@ func NewHTTPServeWithoutTimeouts(id string, conf gosec.Config) (gosec.Rule, []as return &httpServeWithoutTimeouts{ pkg: "net/http", calls: []string{"ListenAndServe", "ListenAndServeTLS", "Serve", "ServeTLS"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Use of net/http serve function that has no support for setting timeouts", - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/implicit_aliasing.go b/rules/implicit_aliasing.go index b2668de..d45c94f 100644 --- a/rules/implicit_aliasing.go +++ b/rules/implicit_aliasing.go @@ -5,10 +5,11 @@ import ( "go/token" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type implicitAliasing struct { - gosec.MetaData + issue.MetaData aliases map[*ast.Object]struct{} rightBrace token.Pos acceptableAlias []*ast.UnaryExpr @@ -27,7 +28,7 @@ func containsUnary(exprs []*ast.UnaryExpr, expr *ast.UnaryExpr) bool { return false } -func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.RangeStmt: // When presented with a range statement, get the underlying Object bound to @@ -73,7 +74,7 @@ func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, er // If we find a unary op of & (reference) of an object within r.aliases, complain. if ident, ok := node.X.(*ast.Ident); ok && node.Op.String() == "&" { if _, contains := r.aliases[ident.Obj]; contains { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } case *ast.ReturnStmt: @@ -94,10 +95,10 @@ func NewImplicitAliasing(id string, conf gosec.Config) (gosec.Rule, []ast.Node) aliases: make(map[*ast.Object]struct{}), rightBrace: token.NoPos, acceptableAlias: make([]*ast.UnaryExpr, 0), - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.Medium, + Severity: issue.Medium, + Confidence: issue.Medium, What: "Implicit memory aliasing in for loop.", }, }, []ast.Node{(*ast.RangeStmt)(nil), (*ast.UnaryExpr)(nil), (*ast.ReturnStmt)(nil)} diff --git a/rules/integer_overflow.go b/rules/integer_overflow.go index f55211a..71ffdc0 100644 --- a/rules/integer_overflow.go +++ b/rules/integer_overflow.go @@ -19,10 +19,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type integerOverflowCheck struct { - gosec.MetaData + issue.MetaData calls gosec.CallList } @@ -30,7 +31,7 @@ func (i *integerOverflowCheck) ID() string { return i.MetaData.ID } -func (i *integerOverflowCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (i *integerOverflowCheck) Match(node ast.Node, ctx *gosec.Context) (*issue.Issue, error) { var atoiVarObj map[*ast.Object]ast.Node // To check multiple lines, ctx.PassedValues is used to store temporary data. @@ -63,7 +64,7 @@ func (i *integerOverflowCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec. if idt, ok := n.Args[0].(*ast.Ident); ok { if _, ok := atoiVarObj[idt.Obj]; ok { // Detect int32(v) and int16(v) - return gosec.NewIssue(ctx, n, i.ID(), i.What, i.Severity, i.Confidence), nil + return ctx.NewIssue(n, i.ID(), i.What, i.Severity, i.Confidence), nil } } } @@ -78,10 +79,10 @@ func NewIntegerOverflowCheck(id string, conf gosec.Config) (gosec.Rule, []ast.No calls := gosec.NewCallList() calls.Add("strconv", "Atoi") return &integerOverflowCheck{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.High, - Confidence: gosec.Medium, + Severity: issue.High, + Confidence: issue.Medium, What: "Potential Integer overflow made by strconv.Atoi result conversion to int16/32", }, calls: calls, diff --git a/rules/math_big_rat.go b/rules/math_big_rat.go index 69037e1..1aac1fa 100644 --- a/rules/math_big_rat.go +++ b/rules/math_big_rat.go @@ -4,10 +4,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type usingOldMathBig struct { - gosec.MetaData + issue.MetaData calls gosec.CallList } @@ -15,18 +16,18 @@ func (r *usingOldMathBig) ID() string { return r.MetaData.ID } -func (r *usingOldMathBig) Match(node ast.Node, ctx *gosec.Context) (gi *gosec.Issue, err error) { +func (r *usingOldMathBig) Match(node ast.Node, ctx *gosec.Context) (gi *issue.Issue, err error) { if callExpr := r.calls.ContainsPkgCallExpr(node, ctx, false); callExpr == nil { return nil, nil } - confidence := gosec.Low + confidence := issue.Low major, minor, build := gosec.GoVersion() if major == 1 && (minor == 16 && build < 14 || minor == 17 && build < 7) { - confidence = gosec.Medium + confidence = issue.Medium } - return gosec.NewIssue(ctx, node, r.ID(), r.What, r.Severity, confidence), nil + return ctx.NewIssue(node, r.ID(), r.What, r.Severity, confidence), nil } // NewUsingOldMathBig rule detects the use of Rat.SetString from math/big. @@ -35,10 +36,10 @@ func NewUsingOldMathBig(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { calls.Add("math/big.Rat", "SetString") return &usingOldMathBig{ calls: calls, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772)", - Severity: gosec.High, + Severity: issue.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/pprof.go b/rules/pprof.go index 4c99af7..8995fc6 100644 --- a/rules/pprof.go +++ b/rules/pprof.go @@ -4,10 +4,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type pprofCheck struct { - gosec.MetaData + issue.MetaData importPath string importName string } @@ -18,10 +19,10 @@ func (p *pprofCheck) ID() string { } // Match checks for pprof imports -func (p *pprofCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (p *pprofCheck) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node, ok := n.(*ast.ImportSpec); ok { if p.importPath == unquote(node.Path.Value) && node.Name != nil && p.importName == node.Name.Name { - return gosec.NewIssue(c, node, p.ID(), p.What, p.Severity, p.Confidence), nil + return c.NewIssue(node, p.ID(), p.What, p.Severity, p.Confidence), nil } } return nil, nil @@ -30,10 +31,10 @@ func (p *pprofCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { // NewPprofCheck detects when the profiling endpoint is automatically exposed func NewPprofCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &pprofCheck{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.High, - Confidence: gosec.High, + Severity: issue.High, + Confidence: issue.High, What: "Profiling endpoint is automatically exposed on /debug/pprof", }, importPath: "net/http/pprof", diff --git a/rules/rand.go b/rules/rand.go index 055adce..8f9bd3b 100644 --- a/rules/rand.go +++ b/rules/rand.go @@ -18,10 +18,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type weakRand struct { - gosec.MetaData + issue.MetaData funcNames []string packagePath string } @@ -30,10 +31,10 @@ func (w *weakRand) ID() string { return w.MetaData.ID } -func (w *weakRand) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (w *weakRand) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { for _, funcName := range w.funcNames { if _, matched := gosec.MatchCallByPackage(n, c, w.packagePath, funcName); matched { - return gosec.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil + return c.NewIssue(n, w.ID(), w.What, w.Severity, w.Confidence), nil } } @@ -48,10 +49,10 @@ func NewWeakRandCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { "Int31n", "Int63", "Int63n", "Intn", "NormalFloat64", "Uint32", "Uint64", }, packagePath: "math/rand", - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.High, - Confidence: gosec.Medium, + Severity: issue.High, + Confidence: issue.Medium, What: "Use of weak random number generator (math/rand instead of crypto/rand)", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/readfile.go b/rules/readfile.go index 8dcf053..cbd1603 100644 --- a/rules/readfile.go +++ b/rules/readfile.go @@ -19,10 +19,11 @@ import ( "go/types" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type readfile struct { - gosec.MetaData + issue.MetaData gosec.CallList pathJoin gosec.CallList clean gosec.CallList @@ -86,7 +87,7 @@ func (r *readfile) trackFilepathClean(n ast.Node) { } // Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile` -func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *readfile) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node := r.clean.ContainsPkgCallExpr(n, c, false); node != nil { r.trackFilepathClean(n) return nil, nil @@ -96,14 +97,14 @@ func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { // eg. os.Open(filepath.Join("/tmp/", file)) if callExpr, ok := arg.(*ast.CallExpr); ok { if r.isJoinFunc(callExpr, c) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } // handles binary string concatenation eg. ioutil.Readfile("/tmp/" + file + "/blob") if binExp, ok := arg.(*ast.BinaryExpr); ok { // resolve all found identities from the BinaryExpr if _, ok := gosec.FindVarIdentities(binExp, c); ok { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } @@ -112,7 +113,7 @@ func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) && !r.isFilepathClean(ident, c) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -126,11 +127,11 @@ func NewReadFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { pathJoin: gosec.NewCallList(), clean: gosec.NewCallList(), CallList: gosec.NewCallList(), - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential file inclusion via variable", - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, }, cleanedVar: map[any]ast.Node{}, } diff --git a/rules/rsa.go b/rules/rsa.go index f2ed5db..62e3e62 100644 --- a/rules/rsa.go +++ b/rules/rsa.go @@ -19,10 +19,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type weakKeyStrength struct { - gosec.MetaData + issue.MetaData calls gosec.CallList bits int } @@ -31,10 +32,10 @@ func (w *weakKeyStrength) ID() string { return w.MetaData.ID } -func (w *weakKeyStrength) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (w *weakKeyStrength) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if callExpr := w.calls.ContainsPkgCallExpr(n, c, false); callExpr != nil { if bits, err := gosec.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) { - return gosec.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil + return c.NewIssue(n, w.ID(), w.What, w.Severity, w.Confidence), nil } } return nil, nil @@ -48,10 +49,10 @@ func NewWeakKeyStrength(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &weakKeyStrength{ calls: calls, bits: bits, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: fmt.Sprintf("RSA keys should be at least %d bits", bits), }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/slowloris.go b/rules/slowloris.go index 60b5e95..f67b58a 100644 --- a/rules/slowloris.go +++ b/rules/slowloris.go @@ -18,10 +18,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type slowloris struct { - gosec.MetaData + issue.MetaData } func (r *slowloris) ID() string { @@ -44,13 +45,13 @@ func containsReadHeaderTimeout(node *ast.CompositeLit) bool { return false } -func (r *slowloris) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (r *slowloris) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch node := n.(type) { case *ast.CompositeLit: actualType := ctx.Info.TypeOf(node.Type) if actualType != nil && actualType.String() == "net/http.Server" { if !containsReadHeaderTimeout(node) { - return gosec.NewIssue(ctx, node, r.ID(), r.What, r.Severity, r.Confidence), nil + return ctx.NewIssue(node, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -60,11 +61,11 @@ func (r *slowloris) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) // NewSlowloris attempts to find the http.Server struct and check if the ReadHeaderTimeout is configured. func NewSlowloris(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &slowloris{ - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential Slowloris Attack because ReadHeaderTimeout is not configured in the http.Server", - Confidence: gosec.Low, - Severity: gosec.Medium, + Confidence: issue.Low, + Severity: issue.Medium, }, }, []ast.Node{(*ast.CompositeLit)(nil)} } diff --git a/rules/sql.go b/rules/sql.go index ee99737..5d15872 100644 --- a/rules/sql.go +++ b/rules/sql.go @@ -20,10 +20,11 @@ import ( "regexp" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type sqlStatement struct { - gosec.MetaData + issue.MetaData gosec.CallList // Contains a list of patterns which must all match for the rule to match. @@ -113,7 +114,7 @@ func (s *sqlStrConcat) checkObject(n *ast.Ident, c *gosec.Context) bool { } // checkQuery verifies if the query parameters is a string concatenation -func (s *sqlStrConcat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*gosec.Issue, error) { +func (s *sqlStrConcat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*issue.Issue, error) { query, err := findQueryArg(call, ctx) if err != nil { return nil, err @@ -134,7 +135,7 @@ func (s *sqlStrConcat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*gose if op, ok := op.(*ast.Ident); ok && s.checkObject(op, ctx) { continue } - return gosec.NewIssue(ctx, be, s.ID(), s.What, s.Severity, s.Confidence), nil + return ctx.NewIssue(be, s.ID(), s.What, s.Severity, s.Confidence), nil } } } @@ -143,7 +144,7 @@ func (s *sqlStrConcat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*gose } // Checks SQL query concatenation issues such as "SELECT * FROM table WHERE " + " ' OR 1=1" -func (s *sqlStrConcat) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (s *sqlStrConcat) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch stmt := n.(type) { case *ast.AssignStmt: for _, expr := range stmt.Rhs { @@ -166,10 +167,10 @@ func NewSQLStrConcat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { patterns: []*regexp.Regexp{ regexp.MustCompile(`(?i)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `), }, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "SQL string concatenation", }, CallList: gosec.NewCallList(), @@ -212,7 +213,7 @@ func (s *sqlStrFormat) constObject(e ast.Expr, c *gosec.Context) bool { return false } -func (s *sqlStrFormat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*gosec.Issue, error) { +func (s *sqlStrFormat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*issue.Issue, error) { query, err := findQueryArg(call, ctx) if err != nil { return nil, err @@ -233,7 +234,7 @@ func (s *sqlStrFormat) checkQuery(call *ast.CallExpr, ctx *gosec.Context) (*gose return nil, nil } -func (s *sqlStrFormat) checkFormatting(n ast.Node, ctx *gosec.Context) *gosec.Issue { +func (s *sqlStrFormat) checkFormatting(n ast.Node, ctx *gosec.Context) *issue.Issue { // argIndex changes the function argument which gets matched to the regex argIndex := 0 if node := s.fmtCalls.ContainsPkgCallExpr(n, ctx, false); node != nil { @@ -286,14 +287,14 @@ func (s *sqlStrFormat) checkFormatting(n ast.Node, ctx *gosec.Context) *gosec.Is } } if s.MatchPatterns(formatter) { - return gosec.NewIssue(ctx, n, s.ID(), s.What, s.Severity, s.Confidence) + return ctx.NewIssue(n, s.ID(), s.What, s.Severity, s.Confidence) } } return nil } // Check SQL query formatting issues such as "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)" -func (s *sqlStrFormat) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { +func (s *sqlStrFormat) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) { switch stmt := n.(type) { case *ast.AssignStmt: for _, expr := range stmt.Rhs { @@ -334,10 +335,10 @@ func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { regexp.MustCompile("(?i)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE)( |\n|\r|\t)"), regexp.MustCompile("%[^bdoxXfFp]"), }, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "SQL string formatting", }, }, diff --git a/rules/ssh.go b/rules/ssh.go index 01f37da..17dfa80 100644 --- a/rules/ssh.go +++ b/rules/ssh.go @@ -4,10 +4,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type sshHostKey struct { - gosec.MetaData + issue.MetaData pkg string calls []string } @@ -16,9 +17,9 @@ func (r *sshHostKey) ID() string { return r.MetaData.ID } -func (r *sshHostKey) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { +func (r *sshHostKey) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err error) { if _, matches := gosec.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } return nil, nil } @@ -28,11 +29,11 @@ func NewSSHHostKey(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &sshHostKey{ pkg: "golang.org/x/crypto/ssh", calls: []string{"InsecureIgnoreHostKey"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Use of ssh InsecureIgnoreHostKey should be audited", - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/ssrf.go b/rules/ssrf.go index 86bb827..9cba2a6 100644 --- a/rules/ssrf.go +++ b/rules/ssrf.go @@ -5,10 +5,11 @@ import ( "go/types" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type ssrf struct { - gosec.MetaData + issue.MetaData gosec.CallList } @@ -40,11 +41,11 @@ func (r *ssrf) ResolveVar(n *ast.CallExpr, c *gosec.Context) bool { } // Match inspects AST nodes to determine if certain net/http methods are called with variable input -func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { // Call expression is using http package directly if node := r.ContainsPkgCallExpr(n, c, false); node != nil { if r.ResolveVar(node, c) { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } return nil, nil @@ -54,11 +55,11 @@ func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { func NewSSRFCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { rule := &ssrf{ CallList: gosec.NewCallList(), - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Potential HTTP request made with variable url", - Severity: gosec.Medium, - Confidence: gosec.Medium, + Severity: issue.Medium, + Confidence: issue.Medium, }, } rule.AddAll("net/http", "Do", "Get", "Head", "Post", "PostForm", "RoundTrip") diff --git a/rules/subproc.go b/rules/subproc.go index 2b6cb18..5f96a73 100644 --- a/rules/subproc.go +++ b/rules/subproc.go @@ -19,10 +19,11 @@ import ( "go/types" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type subprocess struct { - gosec.MetaData + issue.MetaData gosec.CallList } @@ -39,7 +40,7 @@ func (r *subprocess) ID() string { // is unsafe. For example: // // syscall.Exec("echo", "foobar" + tainted) -func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node := r.ContainsPkgCallExpr(n, c, false); node != nil { args := node.Args if r.isContext(n, c) { @@ -64,7 +65,7 @@ func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { _, assignment := ident.Obj.Decl.(*ast.AssignStmt) if variable && assignment { if !gosec.TryResolve(ident, c) { - return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gosec.Medium, gosec.High), nil + return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil } } case *ast.Field: @@ -74,21 +75,21 @@ func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { vv, vvok := obj.(*types.Var) if vvok && vv.Parent().Lookup(ident.Name) == nil { - return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gosec.Medium, gosec.High), nil + return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil } } case *ast.ValueSpec: _, valueSpec := ident.Obj.Decl.(*ast.ValueSpec) if variable && valueSpec { if !gosec.TryResolve(ident, c) { - return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gosec.Medium, gosec.High), nil + return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil } } } } } else if !gosec.TryResolve(arg, c) { // the arg is not a constant or a variable but instead a function call or os.Args[i] - return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with a potential tainted input or cmd arguments", gosec.Medium, gosec.High), nil + return c.NewIssue(n, r.ID(), "Subprocess launched with a potential tainted input or cmd arguments", issue.Medium, issue.High), nil } } } @@ -110,7 +111,7 @@ func (r *subprocess) isContext(n ast.Node, ctx *gosec.Context) bool { // NewSubproc detects cases where we are forking out to an external process func NewSubproc(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { - rule := &subprocess{gosec.MetaData{ID: id}, gosec.NewCallList()} + rule := &subprocess{issue.MetaData{ID: id}, gosec.NewCallList()} rule.Add("os/exec", "Command") rule.Add("os/exec", "CommandContext") rule.Add("syscall", "Exec") diff --git a/rules/tempfiles.go b/rules/tempfiles.go index 63822c0..bdeabcd 100644 --- a/rules/tempfiles.go +++ b/rules/tempfiles.go @@ -19,10 +19,11 @@ import ( "regexp" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type badTempFile struct { - gosec.MetaData + issue.MetaData calls gosec.CallList args *regexp.Regexp argCalls gosec.CallList @@ -33,15 +34,15 @@ func (t *badTempFile) ID() string { return t.MetaData.ID } -func (t *badTempFile) findTempDirArgs(n ast.Node, c *gosec.Context, suspect ast.Node) *gosec.Issue { +func (t *badTempFile) findTempDirArgs(n ast.Node, c *gosec.Context, suspect ast.Node) *issue.Issue { if s, e := gosec.GetString(suspect); e == nil { if t.args.MatchString(s) { - return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence) + return c.NewIssue(n, t.ID(), t.What, t.Severity, t.Confidence) } return nil } if ce := t.argCalls.ContainsPkgCallExpr(suspect, c, false); ce != nil { - return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence) + return c.NewIssue(n, t.ID(), t.What, t.Severity, t.Confidence) } if be, ok := suspect.(*ast.BinaryExpr); ok { if ops := gosec.GetBinaryExprOperands(be); len(ops) != 0 { @@ -55,7 +56,7 @@ func (t *badTempFile) findTempDirArgs(n ast.Node, c *gosec.Context, suspect ast. return nil } -func (t *badTempFile) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { +func (t *badTempFile) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err error) { if node := t.calls.ContainsPkgCallExpr(n, c, false); node != nil { return t.findTempDirArgs(n, c, node.Args[0]), nil } @@ -77,10 +78,10 @@ func NewBadTempFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { args: regexp.MustCompile(`^(/(usr|var))?/tmp(/.*)?$`), argCalls: argCalls, nestedCalls: nestedCalls, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "File creation in shared tmp directory without using ioutil.Tempfile", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/templates.go b/rules/templates.go index 1eec7fb..8836312 100644 --- a/rules/templates.go +++ b/rules/templates.go @@ -18,10 +18,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type templateCheck struct { - gosec.MetaData + issue.MetaData calls gosec.CallList } @@ -29,11 +30,11 @@ func (t *templateCheck) ID() string { return t.MetaData.ID } -func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if node := t.calls.ContainsPkgCallExpr(n, c, false); node != nil { for _, arg := range node.Args { if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe - return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil + return c.NewIssue(n, t.ID(), t.What, t.Severity, t.Confidence), nil } } } @@ -50,10 +51,10 @@ func NewTemplateCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { calls.Add("html/template", "URL") return &templateCheck{ calls: calls, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.Low, + Severity: issue.Medium, + Confidence: issue.Low, What: "The used method does not auto-escape HTML. This can potentially lead to 'Cross-site Scripting' vulnerabilities, in case the attacker controls the input.", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/tls.go b/rules/tls.go index 1cc3a29..65a0b5a 100644 --- a/rules/tls.go +++ b/rules/tls.go @@ -24,10 +24,11 @@ import ( "strconv" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type insecureConfigTLS struct { - gosec.MetaData + issue.MetaData MinVersion int64 MaxVersion int64 requiredType string @@ -49,13 +50,13 @@ func stringInSlice(a string, list []string) bool { return false } -func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gosec.Context) *gosec.Issue { +func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gosec.Context) *issue.Issue { if ciphers, ok := n.(*ast.CompositeLit); ok { for _, cipher := range ciphers.Elts { if ident, ok := cipher.(*ast.SelectorExpr); ok { if !stringInSlice(ident.Sel.Name, t.goodCiphers) { err := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name) - return gosec.NewIssue(c, ident, t.ID(), err, gosec.High, gosec.High) + return c.NewIssue(ident, t.ID(), err, issue.High, issue.High) } } } @@ -63,7 +64,7 @@ func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gosec.Context) return nil } -func (t *insecureConfigTLS) processTLSConf(n ast.Node, c *gosec.Context) *gosec.Issue { +func (t *insecureConfigTLS) processTLSConf(n ast.Node, c *gosec.Context) *issue.Issue { if kve, ok := n.(*ast.KeyValueExpr); ok { issue := t.processTLSConfVal(kve.Key, kve.Value, c) if issue != nil { @@ -83,27 +84,27 @@ func (t *insecureConfigTLS) processTLSConf(n ast.Node, c *gosec.Context) *gosec. return nil } -func (t *insecureConfigTLS) processTLSConfVal(key ast.Expr, value ast.Expr, c *gosec.Context) *gosec.Issue { +func (t *insecureConfigTLS) processTLSConfVal(key ast.Expr, value ast.Expr, c *gosec.Context) *issue.Issue { if ident, ok := key.(*ast.Ident); ok { switch ident.Name { case "InsecureSkipVerify": if node, ok := value.(*ast.Ident); ok { if node.Name != "false" { - return gosec.NewIssue(c, value, t.ID(), "TLS InsecureSkipVerify set true.", gosec.High, gosec.High) + return c.NewIssue(value, t.ID(), "TLS InsecureSkipVerify set true.", issue.High, issue.High) } } else { // TODO(tk): symbol tab look up to get the actual value - return gosec.NewIssue(c, value, t.ID(), "TLS InsecureSkipVerify may be true.", gosec.High, gosec.Low) + return c.NewIssue(value, t.ID(), "TLS InsecureSkipVerify may be true.", issue.High, issue.Low) } case "PreferServerCipherSuites": if node, ok := value.(*ast.Ident); ok { if node.Name == "false" { - return gosec.NewIssue(c, value, t.ID(), "TLS PreferServerCipherSuites set false.", gosec.Medium, gosec.High) + return c.NewIssue(value, t.ID(), "TLS PreferServerCipherSuites set false.", issue.Medium, issue.High) } } else { // TODO(tk): symbol tab look up to get the actual value - return gosec.NewIssue(c, value, t.ID(), "TLS PreferServerCipherSuites may be false.", gosec.Medium, gosec.Low) + return c.NewIssue(value, t.ID(), "TLS PreferServerCipherSuites may be false.", issue.Medium, issue.Low) } case "MinVersion": @@ -188,16 +189,16 @@ func (t *insecureConfigTLS) mapVersion(version string) int64 { return v } -func (t *insecureConfigTLS) checkVersion(n ast.Node, c *gosec.Context) *gosec.Issue { +func (t *insecureConfigTLS) checkVersion(n ast.Node, c *gosec.Context) *issue.Issue { if t.actualMaxVersion == 0 && t.actualMinVersion >= t.MinVersion { // no warning is generated since the min version is greater than the secure min version return nil } if t.actualMinVersion < t.MinVersion { - return gosec.NewIssue(c, n, t.ID(), "TLS MinVersion too low.", gosec.High, gosec.High) + return c.NewIssue(n, t.ID(), "TLS MinVersion too low.", issue.High, issue.High) } if t.actualMaxVersion < t.MaxVersion { - return gosec.NewIssue(c, n, t.ID(), "TLS MaxVersion too low.", gosec.High, gosec.High) + return c.NewIssue(n, t.ID(), "TLS MaxVersion too low.", issue.High, issue.High) } return nil } @@ -207,7 +208,7 @@ func (t *insecureConfigTLS) resetVersion() { t.actualMinVersion = 0 } -func (t *insecureConfigTLS) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (t *insecureConfigTLS) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil { actualType := c.Info.TypeOf(complit.Type) if actualType != nil && actualType.String() == t.requiredType { diff --git a/rules/tls_config.go b/rules/tls_config.go index 9bb17c2..8a25518 100644 --- a/rules/tls_config.go +++ b/rules/tls_config.go @@ -4,13 +4,14 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) // NewModernTLSCheck creates a check for Modern TLS ciphers // DO NOT EDIT - generated by tlsconfig tool func NewModernTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gosec.MetaData{ID: id}, + MetaData: issue.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0304, MaxVersion: 0x0304, @@ -26,7 +27,7 @@ func NewModernTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { // DO NOT EDIT - generated by tlsconfig tool func NewIntermediateTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gosec.MetaData{ID: id}, + MetaData: issue.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0303, MaxVersion: 0x0304, @@ -52,7 +53,7 @@ func NewIntermediateTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.No // DO NOT EDIT - generated by tlsconfig tool func NewOldTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gosec.MetaData{ID: id}, + MetaData: issue.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0301, MaxVersion: 0x0304, diff --git a/rules/unsafe.go b/rules/unsafe.go index 88a298f..133ba1e 100644 --- a/rules/unsafe.go +++ b/rules/unsafe.go @@ -18,10 +18,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type usingUnsafe struct { - gosec.MetaData + issue.MetaData pkg string calls []string } @@ -30,9 +31,9 @@ func (r *usingUnsafe) ID() string { return r.MetaData.ID } -func (r *usingUnsafe) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { +func (r *usingUnsafe) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err error) { if _, matches := gosec.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } return nil, nil } @@ -43,11 +44,11 @@ func NewUsingUnsafe(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &usingUnsafe{ pkg: "unsafe", calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"}, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, What: "Use of unsafe calls should be audited", - Severity: gosec.Low, - Confidence: gosec.High, + Severity: issue.Low, + Confidence: issue.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/weakcrypto.go b/rules/weakcrypto.go index eecb88f..d6910db 100644 --- a/rules/weakcrypto.go +++ b/rules/weakcrypto.go @@ -18,10 +18,11 @@ import ( "go/ast" "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" ) type usesWeakCryptography struct { - gosec.MetaData + issue.MetaData blocklist map[string][]string } @@ -29,10 +30,10 @@ func (r *usesWeakCryptography) ID() string { return r.MetaData.ID } -func (r *usesWeakCryptography) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { +func (r *usesWeakCryptography) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { for pkg, funcs := range r.blocklist { if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { - return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } return nil, nil @@ -47,10 +48,10 @@ func NewUsesWeakCryptography(id string, conf gosec.Config) (gosec.Rule, []ast.No calls["crypto/rc4"] = []string{"NewCipher"} rule := &usesWeakCryptography{ blocklist: calls, - MetaData: gosec.MetaData{ + MetaData: issue.MetaData{ ID: id, - Severity: gosec.Medium, - Confidence: gosec.High, + Severity: issue.Medium, + Confidence: issue.High, What: "Use of weak cryptographic primitive", }, }