github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/gosec.go (about) 1 package golinters 2 3 import ( 4 "fmt" 5 "go/token" 6 "io" 7 "log" 8 "strconv" 9 "strings" 10 "sync" 11 12 "github.com/pkg/errors" 13 "github.com/securego/gosec/v2" 14 "github.com/securego/gosec/v2/rules" 15 "golang.org/x/tools/go/analysis" 16 "golang.org/x/tools/go/packages" 17 18 "github.com/golangci/golangci-lint/pkg/config" 19 "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" 20 "github.com/golangci/golangci-lint/pkg/lint/linter" 21 "github.com/golangci/golangci-lint/pkg/result" 22 ) 23 24 const gosecName = "gosec" 25 26 func NewGosec(settings *config.GoSecSettings) *goanalysis.Linter { 27 var mu sync.Mutex 28 var resIssues []goanalysis.Issue 29 30 conf := gosec.NewConfig() 31 32 var filters []rules.RuleFilter 33 if settings != nil { 34 filters = gosecRuleFilters(settings.Includes, settings.Excludes) 35 36 for k, v := range settings.Config { 37 if k != gosec.Globals { 38 // Uses ToUpper because the parsing of the map's key change the key to lowercase. 39 // The value is not impacted by that: the case is respected. 40 k = strings.ToUpper(k) 41 } 42 conf.Set(k, v) 43 } 44 } 45 46 logger := log.New(io.Discard, "", 0) 47 48 ruleDefinitions := rules.Generate(false, filters...) 49 50 analyzer := &analysis.Analyzer{ 51 Name: gosecName, 52 Doc: goanalysis.TheOnlyanalyzerDoc, 53 Run: goanalysis.DummyRun, 54 } 55 56 return goanalysis.NewLinter( 57 gosecName, 58 "Inspects source code for security problems", 59 []*analysis.Analyzer{analyzer}, 60 nil, 61 ).WithContextSetter(func(lintCtx *linter.Context) { 62 analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { 63 // The `gosecAnalyzer` is here because of concurrency issue. 64 gosecAnalyzer := gosec.NewAnalyzer(conf, true, settings.ExcludeGenerated, false, settings.Concurrency, logger) 65 gosecAnalyzer.LoadRules(ruleDefinitions.RulesInfo()) 66 67 issues := runGoSec(lintCtx, pass, settings, gosecAnalyzer) 68 69 mu.Lock() 70 resIssues = append(resIssues, issues...) 71 mu.Unlock() 72 73 return nil, nil 74 } 75 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 76 return resIssues 77 }).WithLoadMode(goanalysis.LoadModeTypesInfo) 78 } 79 80 func runGoSec(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoSecSettings, analyzer *gosec.Analyzer) []goanalysis.Issue { 81 pkg := &packages.Package{ 82 Fset: pass.Fset, 83 Syntax: pass.Files, 84 Types: pass.Pkg, 85 TypesInfo: pass.TypesInfo, 86 } 87 88 analyzer.Check(pkg) 89 90 secIssues, _, _ := analyzer.Report() 91 if len(secIssues) == 0 { 92 return nil 93 } 94 95 severity, err := convertToScore(settings.Severity) 96 if err != nil { 97 lintCtx.Log.Warnf("The provided severity %v", err) 98 } 99 100 confidence, err := convertToScore(settings.Confidence) 101 if err != nil { 102 lintCtx.Log.Warnf("The provided confidence %v", err) 103 } 104 105 secIssues = filterIssues(secIssues, severity, confidence) 106 107 issues := make([]goanalysis.Issue, 0, len(secIssues)) 108 for _, i := range secIssues { 109 text := fmt.Sprintf("%s: %s", i.RuleID, i.What) // TODO: use severity and confidence 110 111 var r *result.Range 112 113 line, err := strconv.Atoi(i.Line) 114 if err != nil { 115 r = &result.Range{} 116 if n, rerr := fmt.Sscanf(i.Line, "%d-%d", &r.From, &r.To); rerr != nil || n != 2 { 117 lintCtx.Log.Warnf("Can't convert gosec line number %q of %v to int: %s", i.Line, i, err) 118 continue 119 } 120 line = r.From 121 } 122 123 column, err := strconv.Atoi(i.Col) 124 if err != nil { 125 lintCtx.Log.Warnf("Can't convert gosec column number %q of %v to int: %s", i.Col, i, err) 126 continue 127 } 128 129 issues = append(issues, goanalysis.NewIssue(&result.Issue{ 130 Pos: token.Position{ 131 Filename: i.File, 132 Line: line, 133 Column: column, 134 }, 135 Text: text, 136 LineRange: r, 137 FromLinter: gosecName, 138 }, pass)) 139 } 140 141 return issues 142 } 143 144 // based on https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/cmd/gosec/main.go#L170-L188 145 func gosecRuleFilters(includes, excludes []string) []rules.RuleFilter { 146 var filters []rules.RuleFilter 147 148 if len(includes) > 0 { 149 filters = append(filters, rules.NewRuleFilter(false, includes...)) 150 } 151 152 if len(excludes) > 0 { 153 filters = append(filters, rules.NewRuleFilter(true, excludes...)) 154 } 155 156 return filters 157 } 158 159 // code borrowed from https://github.com/securego/gosec/blob/69213955dacfd560562e780f723486ef1ca6d486/cmd/gosec/main.go#L250-L262 160 func convertToScore(str string) (gosec.Score, error) { 161 str = strings.ToLower(str) 162 switch str { 163 case "", "low": 164 return gosec.Low, nil 165 case "medium": 166 return gosec.Medium, nil 167 case "high": 168 return gosec.High, nil 169 default: 170 return gosec.Low, errors.Errorf("'%s' is invalid, use low instead. Valid options: low, medium, high", str) 171 } 172 } 173 174 // code borrowed from https://github.com/securego/gosec/blob/69213955dacfd560562e780f723486ef1ca6d486/cmd/gosec/main.go#L264-L276 175 func filterIssues(issues []*gosec.Issue, severity, confidence gosec.Score) []*gosec.Issue { 176 res := make([]*gosec.Issue, 0) 177 for _, issue := range issues { 178 if issue.Severity >= severity && issue.Confidence >= confidence { 179 res = append(res, issue) 180 } 181 } 182 return res 183 }