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  }