diff --git a/.github/issue_template.md b/.github/issue_template.md index 14aec56..9c3ef02 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -2,7 +2,7 @@ ### Steps to reproduce the behavior -### Gas version +### gosec version ### Go version (output of 'go version') diff --git a/.travis.yml b/.travis.yml index 99640c0..d12ef10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.8 - 1.9 - "1.10" - tip @@ -12,7 +11,7 @@ install: - go get -u github.com/onsi/ginkgo/ginkgo - go get -u github.com/onsi/gomega - go get -u golang.org/x/crypto/ssh - - go get -u github.com/GoASTScanner/gas/cmd/gas/... + - go get -u github.com/securego/gosec/cmd/gosec/... - go get -v -t ./... - export PATH=$PATH:$HOME/gopath/bin diff --git a/Dockerfile b/Dockerfile index bbe12ca..e2ff565 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.9.4-alpine3.7 -ENV BIN=gas +ENV BIN=gosec COPY build/*-linux-amd64 /go/bin/$BIN COPY docker-entrypoint.sh /usr/local/bin diff --git a/Makefile b/Makefile index db55f0e..fc6002b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GIT_TAG?= $(shell git describe --always --tags) BUILD_DATE = $(shell date +%Y-%m-%d) -BIN = gas -BUILD_CMD = go build -ldflags "-X main.Version=${VERSION} -X main.GitTag=${GIT_TAG} -X main.BuildDate=${BUILD_DATE}" -o build/$(BIN)-$(VERSION)-$${GOOS}-$${GOARCH} ./cmd/gas/ & +BIN = gosec +BUILD_CMD = go build -ldflags "-X main.Version=${VERSION} -X main.GitTag=${GIT_TAG} -X main.BuildDate=${BUILD_DATE}" -o build/$(BIN)-$(VERSION)-$${GOOS}-$${GOARCH} ./cmd/gosec/ & FMT_CMD = $(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr) IMAGE_REPO = docker.io @@ -13,12 +13,12 @@ test: bootstrap test -z '$(FMT_CMD)' go vet $(go list ./... | grep -v /vendor/) golint -set_exit_status $(shell go list ./... | grep -v vendor) - gas ./... + gosec ./... ginkgo -r -v bootstrap: dep ensure build: - go build -o $(BIN) ./cmd/gas/ + go build -o $(BIN) ./cmd/gosec/ clean: rm -rf build vendor rm -f release image bootstrap $(BIN) diff --git a/README.md b/README.md index 5646d9b..830d330 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -## GAS - Go AST Scanner +## gosec -Golang Security Checker Inspects source code for security problems by scanning the Go AST. @@ -12,26 +12,25 @@ You may obtain a copy of the License [here](http://www.apache.org/licenses/LICEN ### Project status -[![Build Status](https://travis-ci.org/GoASTScanner/gas.svg?branch=master)](https://travis-ci.org/GoASTScanner/gas) -[![GoDoc](https://godoc.org/github.com/GoASTScanner/gas?status.svg)](https://godoc.org/github.com/GoASTScanner/gas) +[![Build Status](https://travis-ci.org/securego/gosec.svg?branch=master)](https://travis-ci.org/securego/gosec) +[![GoDoc](https://godoc.org/github.com/securego/gosec?status.svg)](https://godoc.org/github.com/securego/gosec) +[![Slack](http://securego.herokuapp.com/badge.svg)](http://securego.herokuapp.com) -Gas is still in alpha and accepting feedback from early adopters. We do -not consider it production ready at this time. ### Install -`$ go get github.com/GoASTScanner/gas/cmd/gas/...` +`$ go get github.com/securego/gosec/cmd/gosec/...` ### Usage -Gas can be configured to only run a subset of rules, to exclude certain file +Gosec can be configured to only run a subset of rules, to exclude certain file paths, and produce reports in different formats. By default all rules will be run against the supplied input files. To recursively scan from the current directory you can supply './...' as the input argument. #### Selecting rules -By default Gas will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-include=' flag, +By default gosec will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-include=' flag, or to specify a set of rules to explicitly exclude using the '-exclude=' flag. ##### Available rules @@ -50,6 +49,7 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag. - G302: Poor file permisions used with chmod - G303: Creating tempfile using a predictable path - G304: File path provided as taint input + - G305: File traversal when extracting zip archive - G401: Detect the usage of DES, RC4, or MD5 - G402: Look for bad TLS connection settings - G403: Ensure minimum RSA key length of 2048 bits @@ -62,22 +62,22 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag. ``` # Run a specific set of rules -$ gas -include=G101,G203,G401 ./... +$ gosec -include=G101,G203,G401 ./... # Run everything except for rule G303 -$ gas -exclude=G303 ./... +$ gosec -exclude=G303 ./... ``` #### Excluding files: -Gas will ignore dependencies in your vendor directory any files +gosec will ignore dependencies in your vendor directory any files that are not considered build artifacts by the compiler (so test files). #### Annotating code -As with all automated detection tools there will be cases of false positives. In cases where Gas reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment. +As with all automated detection tools there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment. -The annotation causes Gas to stop processing any further nodes within the +The annotation causes gosec to stop processing any further nodes within the AST so can apply to a whole block or more granularly to a single expression. ```go @@ -101,26 +101,26 @@ have been used. To run the scanner and ignore any #nosec annotations you can do the following: ``` -$ gas -nosec=true ./... +$ gosec -nosec=true ./... ``` #### Build tags -Gas is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer. +gosec is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer. They can be provided as a comma separated list as follows: ``` -$ gas -tag debug,ignore ./... +$ gosec -tag debug,ignore ./... ``` ### Output formats -Gas currently supports text, json, yaml, csv and JUnit XML output formats. By default +gosec currently supports text, json, yaml, csv and JUnit XML output formats. By default results will be reported to stdout, but can also be written to an output file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows: ``` # Write output in json format to results.json -$ gas -fmt=json -out=results.json *.go +$ gosec -fmt=json -out=results.json *.go ``` ### Development @@ -143,7 +143,7 @@ make test #### Release Build -Gas can be released as follows: +gosec can be released as follows: ```bash make release VERSION=2.0.0 @@ -152,11 +152,11 @@ make release VERSION=2.0.0 The released version of the tool is available in the `build` folder. The build information should be displayed in the usage text. ``` -./build/gas-2.0.0-linux-amd64 -h +./build/gosec-2.0.0-linux-amd64 -h -GAS - Go AST Scanner +gosec - Golang security checker -Gas analyzes Go source code to look for common programming mistakes that +gosec analyzes Go source code to look for common programming mistakes that can lead to security problems. VERSION: 2.0.0 @@ -173,10 +173,10 @@ You can execute a release and build the docker image as follows: make image VERSION=2.0.0 ``` -Now you can run the gas tool in a container against your local workspace: +Now you can run the gosec tool in a container against your local workspace: ``` -docker run -it -v :/workspace gas /workspace +docker run -it -v :/workspace gosec /workspace ``` #### Generate TLS rule @@ -187,7 +187,7 @@ The configuration of TLS rule can be generated from [Mozilla's TLS ciphers recom First you need to install the generator tool: ``` -go get github.com/GoASTScanner/gas/cmd/tlsconfig/... +go get github.com/securego/gosec/cmd/tlsconfig/... ``` You can invoke now the `go generate` in the root of the project: diff --git a/analyzer.go b/analyzer.go index d3a47ec..231b718 100644 --- a/analyzer.go +++ b/analyzer.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package gas holds the central scanning logic used by GAS -package gas +// Package gosec holds the central scanning logic used by gosec security scanner +package gosec import ( "go/ast" @@ -28,8 +28,6 @@ import ( "regexp" "strings" - "path/filepath" - "golang.org/x/tools/go/loader" ) @@ -55,7 +53,7 @@ type Metrics struct { NumFound int `json:"found"` } -// Analyzer object is the main object of GAS. It has methods traverse an AST +// Analyzer object is the main object of gosec. It has methods traverse an AST // and invoke the correct checking rules as on each node as required. type Analyzer struct { ignoreNosec bool @@ -74,7 +72,7 @@ func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer { ignoreNoSec = setting == "true" || setting == "enabled" } if logger == nil { - logger = log.New(os.Stderr, "[gas]", log.LstdFlags) + logger = log.New(os.Stderr, "[gosec]", log.LstdFlags) } return &Analyzer{ ignoreNosec: ignoreNoSec, @@ -89,15 +87,15 @@ func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer { // LoadRules instantiates all the rules to be used when analyzing source // packages -func (gas *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) { +func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) { for id, def := range ruleDefinitions { - r, nodes := def(id, gas.config) - gas.ruleset.Register(r, nodes...) + r, nodes := def(id, gosec.config) + gosec.ruleset.Register(r, nodes...) } } // Process kicks off the analysis process for a given package -func (gas *Analyzer) Process(buildTags []string, packagePaths ...string) error { +func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error { ctx := build.Default ctx.BuildTags = append(ctx.BuildTags, buildTags...) packageConfig := loader.Config{ @@ -106,15 +104,12 @@ func (gas *Analyzer) Process(buildTags []string, packagePaths ...string) error { AllowErrors: true, } for _, packagePath := range packagePaths { - abspath, err := filepath.Abs(packagePath) + abspath, err := GetPkgAbsPath(packagePath) if err != nil { - return err - } - if _, err := os.Stat(abspath); os.IsNotExist(err) { - gas.logger.Printf("Skipping: %s. Path doesn't exist.", abspath) + gosec.logger.Printf("Skipping: %s. Path doesn't exist.", abspath) continue } - gas.logger.Println("Searching directory:", abspath) + gosec.logger.Println("Searching directory:", abspath) basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment) if err != nil { @@ -135,31 +130,31 @@ func (gas *Analyzer) Process(buildTags []string, packagePaths ...string) error { } for _, pkg := range builtPackage.Created { - gas.logger.Println("Checking package:", pkg.String()) + gosec.logger.Println("Checking package:", pkg.String()) for _, file := range pkg.Files { - gas.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name()) - gas.context.FileSet = builtPackage.Fset - gas.context.Config = gas.config - gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments) - gas.context.Root = file - gas.context.Info = &pkg.Info - gas.context.Pkg = pkg.Pkg - gas.context.Imports = NewImportTracker() - gas.context.Imports.TrackPackages(gas.context.Pkg.Imports()...) - ast.Walk(gas, file) - gas.stats.NumFiles++ - gas.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount() + gosec.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name()) + gosec.context.FileSet = builtPackage.Fset + gosec.context.Config = gosec.config + gosec.context.Comments = ast.NewCommentMap(gosec.context.FileSet, file, file.Comments) + gosec.context.Root = file + gosec.context.Info = &pkg.Info + gosec.context.Pkg = pkg.Pkg + gosec.context.Imports = NewImportTracker() + gosec.context.Imports.TrackPackages(gosec.context.Pkg.Imports()...) + ast.Walk(gosec, file) + gosec.stats.NumFiles++ + gosec.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount() } } return nil } // ignore a node (and sub-tree) if it is tagged with a "#nosec" comment -func (gas *Analyzer) ignore(n ast.Node) ([]string, bool) { - if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec { +func (gosec *Analyzer) ignore(n ast.Node) ([]string, bool) { + if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec { for _, group := range groups { if strings.Contains(group.Text(), "#nosec") { - gas.stats.NumNosec++ + gosec.stats.NumNosec++ // Pull out the specific rules that are listed to be ignored. re := regexp.MustCompile("(G\\d{3})") @@ -182,27 +177,27 @@ func (gas *Analyzer) ignore(n ast.Node) ([]string, bool) { return nil, false } -// Visit runs the GAS visitor logic over an AST created by parsing go code. +// Visit runs the gosec visitor logic over an AST created by parsing go code. // Rule methods added with AddRule will be invoked as necessary. -func (gas *Analyzer) Visit(n ast.Node) ast.Visitor { +func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { // If we've reached the end of this branch, pop off the ignores stack. if n == nil { - if len(gas.context.Ignores) > 0 { - gas.context.Ignores = gas.context.Ignores[1:] + if len(gosec.context.Ignores) > 0 { + gosec.context.Ignores = gosec.context.Ignores[1:] } - return gas + return gosec } // Get any new rule exclusions. - ignoredRules, ignoreAll := gas.ignore(n) + ignoredRules, ignoreAll := gosec.ignore(n) if ignoreAll { return nil } // Now create the union of exclusions. ignores := make(map[string]bool, 0) - if len(gas.context.Ignores) > 0 { - for k, v := range gas.context.Ignores[0] { + if len(gosec.context.Ignores) > 0 { + for k, v := range gosec.context.Ignores[0] { ignores[k] = v } } @@ -212,37 +207,37 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor { } // Push the new set onto the stack. - gas.context.Ignores = append([]map[string]bool{ignores}, gas.context.Ignores...) + gosec.context.Ignores = append([]map[string]bool{ignores}, gosec.context.Ignores...) // Track aliased and initialization imports - gas.context.Imports.TrackImport(n) + gosec.context.Imports.TrackImport(n) - for _, rule := range gas.ruleset.RegisteredFor(n) { + for _, rule := range gosec.ruleset.RegisteredFor(n) { if _, ok := ignores[rule.ID()]; ok { continue } - issue, err := rule.Match(n, gas.context) + issue, err := rule.Match(n, gosec.context) if err != nil { - file, line := GetLocation(n, gas.context) + file, line := GetLocation(n, gosec.context) file = path.Base(file) - gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line) + gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line) } if issue != nil { - gas.issues = append(gas.issues, issue) - gas.stats.NumFound++ + gosec.issues = append(gosec.issues, issue) + gosec.stats.NumFound++ } } - return gas + return gosec } // Report returns the current issues discovered and the metrics about the scan -func (gas *Analyzer) Report() ([]*Issue, *Metrics) { - return gas.issues, gas.stats +func (gosec *Analyzer) Report() ([]*Issue, *Metrics) { + return gosec.issues, gosec.stats } // Reset clears state such as context, issues and metrics from the configured analyzer -func (gas *Analyzer) Reset() { - gas.context = &Context{} - gas.issues = make([]*Issue, 0, 16) - gas.stats = &Metrics{} +func (gosec *Analyzer) Reset() { + gosec.context = &Context{} + gosec.issues = make([]*Issue, 0, 16) + gosec.stats = &Metrics{} } diff --git a/analyzer_test.go b/analyzer_test.go index c527d0e..5252af1 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -1,4 +1,4 @@ -package gas_test +package gosec_test import ( "io/ioutil" @@ -6,24 +6,24 @@ import ( "os" "strings" - "github.com/GoASTScanner/gas" - "github.com/GoASTScanner/gas/rules" + "github.com/securego/gosec" + "github.com/securego/gosec/rules" - "github.com/GoASTScanner/gas/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec/testutils" ) var _ = Describe("Analyzer", func() { var ( - analyzer *gas.Analyzer + analyzer *gosec.Analyzer logger *log.Logger buildTags []string ) BeforeEach(func() { logger, _ = testutils.NewLogger() - analyzer = gas.NewAnalyzer(nil, logger) + analyzer = gosec.NewAnalyzer(nil, logger) }) Context("when processing a package", func() { @@ -200,9 +200,9 @@ var _ = Describe("Analyzer", func() { source := sample.Code // overwrite nosec option - nosecIgnoreConfig := gas.NewConfig() + nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig.SetGlobal("nosec", "true") - customAnalyzer := gas.NewAnalyzer(nosecIgnoreConfig, logger) + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, logger) customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) nosecPackage := testutils.NewTestPackage() diff --git a/call_list.go b/call_list.go index e277950..8370f8f 100644 --- a/call_list.go +++ b/call_list.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import ( "go/ast" diff --git a/call_list_test.go b/call_list_test.go index f949cfd..41aa51d 100644 --- a/call_list_test.go +++ b/call_list_test.go @@ -1,20 +1,20 @@ -package gas_test +package gosec_test import ( "go/ast" - "github.com/GoASTScanner/gas" - "github.com/GoASTScanner/gas/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" + "github.com/securego/gosec/testutils" ) var _ = Describe("call list", func() { var ( - calls gas.CallList + calls gosec.CallList ) BeforeEach(func() { - calls = gas.NewCallList() + calls = gosec.NewCallList() }) It("should not return any matches when empty", func() { @@ -72,7 +72,7 @@ var _ = Describe("call list", func() { matched := 0 v := testutils.NewMockVisitor() v.Context = ctx - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx) != nil { matched++ } diff --git a/cmd/gas/filelist.go b/cmd/gosec/filelist.go similarity index 100% rename from cmd/gas/filelist.go rename to cmd/gosec/filelist.go diff --git a/cmd/gas/main.go b/cmd/gosec/main.go similarity index 80% rename from cmd/gas/main.go rename to cmd/gosec/main.go index ccc002e..3d6479a 100644 --- a/cmd/gas/main.go +++ b/cmd/gosec/main.go @@ -20,24 +20,22 @@ import ( "io/ioutil" "log" "os" - "os/user" "path/filepath" "regexp" - "runtime" "sort" "strings" - "github.com/GoASTScanner/gas" - "github.com/GoASTScanner/gas/output" - "github.com/GoASTScanner/gas/rules" "github.com/kisielk/gotool" + "github.com/securego/gosec" + "github.com/securego/gosec/output" + "github.com/securego/gosec/rules" ) const ( usageText = ` -GAS - Go AST Scanner +gosec - Golang security checker -Gas analyzes Go source code to look for common programming mistakes that +gosec analyzes Go source code to look for common programming mistakes that can lead to security problems. VERSION: %s @@ -47,17 +45,17 @@ BUILD DATE: %s USAGE: # Check a single package - $ gas $GOPATH/src/github.com/example/project + $ gosec $GOPATH/src/github.com/example/project # Check all packages under the current directory and save results in # json format. - $ gas -fmt=json -out=results.json ./... + $ gosec -fmt=json -out=results.json ./... # Run a specific set of rules (by default all rules will be run): - $ gas -include=G101,G203,G401 ./... + $ gosec -include=G101,G203,G401 ./... # Run all rules except the provided - $ gas -exclude=G101 $GOPATH/src/github.com/example/project/... + $ gosec -exclude=G101 $GOPATH/src/github.com/example/project/... ` ) @@ -119,8 +117,8 @@ func usage() { fmt.Fprint(os.Stderr, "\n") } -func loadConfig(configFile string) (gas.Config, error) { - config := gas.NewConfig() +func loadConfig(configFile string) (gosec.Config, error) { + config := gosec.NewConfig() if configFile != "" { // #nosec file, err := os.Open(configFile) @@ -158,7 +156,7 @@ func loadRules(include, exclude string) rules.RuleList { return rules.Generate(filters...) } -func saveOutput(filename, format string, issues []*gas.Issue, metrics *gas.Metrics) error { +func saveOutput(filename, format string, issues []*gosec.Issue, metrics *gosec.Metrics) error { if filename != "" { outfile, err := os.Create(filename) if err != nil { @@ -178,36 +176,13 @@ func saveOutput(filename, format string, issues []*gas.Issue, metrics *gas.Metri return nil } -func getenv(key, userDefault string) string { - if val := os.Getenv(key); val != "" { - return val - } - return userDefault -} - -func gopath() []string { - defaultGoPath := runtime.GOROOT() - if u, err := user.Current(); err == nil { - defaultGoPath = filepath.Join(u.HomeDir, "go") - } - path := getenv("GOPATH", defaultGoPath) - paths := strings.Split(path, string(os.PathListSeparator)) - for idx, path := range paths { - if abs, err := filepath.Abs(path); err == nil { - paths[idx] = abs - } - } - return paths -} - -func cleanPath(path string, gopaths []string) (string, error) { - +func cleanPath(path string) (string, error) { cleanFailed := fmt.Errorf("%s is not within the $GOPATH and cannot be processed", path) nonRecursivePath := strings.TrimSuffix(path, "/...") // do not attempt to clean directs that are resolvable on gopath if _, err := os.Stat(nonRecursivePath); err != nil && os.IsNotExist(err) { log.Printf("directory %s doesn't exist, checking if is a package on $GOPATH", path) - for _, basedir := range gopaths { + for _, basedir := range gosec.Gopath() { dir := filepath.Join(basedir, "src", nonRecursivePath) if st, err := os.Stat(dir); err == nil && st.IsDir() { log.Printf("located %s in %s", path, dir) @@ -218,24 +193,17 @@ func cleanPath(path string, gopaths []string) (string, error) { } // ensure we resolve package directory correctly based on $GOPATH - abspath, err := filepath.Abs(path) + pkgPath, err := gosec.GetPkgRelativePath(path) if err != nil { - abspath = path + return "", cleanFailed } - for _, base := range gopaths { - projectRoot := filepath.FromSlash(fmt.Sprintf("%s/src/", base)) - if strings.HasPrefix(abspath, projectRoot) { - return strings.TrimPrefix(abspath, projectRoot), nil - } - } - return "", cleanFailed + return pkgPath, nil } func cleanPaths(paths []string) []string { - gopaths := gopath() var clean []string for _, path := range paths { - cleaned, err := cleanPath(path, gopaths) + cleaned, err := cleanPath(path) if err != nil { log.Fatal(err) } @@ -283,7 +251,7 @@ func main() { if *flagQuiet { logger = log.New(ioutil.Discard, "", 0) } else { - logger = log.New(logWriter, "[gas] ", log.LstdFlags) + logger = log.New(logWriter, "[gosec] ", log.LstdFlags) } // Load config @@ -299,14 +267,14 @@ func main() { } // Create the analyzer - analyzer := gas.NewAnalyzer(config, logger) + analyzer := gosec.NewAnalyzer(config, logger) analyzer.LoadRules(ruleDefinitions.Builders()) vendor := regexp.MustCompile(`[\\/]vendor([\\/]|$)`) var packages []string // Iterate over packages on the import paths - gopaths := gopath() + gopaths := gosec.Gopath() for _, pkg := range gotool.ImportPaths(cleanPaths(flag.Args())) { // Skip vendor directory diff --git a/cmd/gas/sort_issues.go b/cmd/gosec/sort_issues.go similarity index 76% rename from cmd/gas/sort_issues.go rename to cmd/gosec/sort_issues.go index 5557f96..10a86eb 100644 --- a/cmd/gas/sort_issues.go +++ b/cmd/gosec/sort_issues.go @@ -3,10 +3,10 @@ package main import ( "sort" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) -type sortBySeverity []*gas.Issue +type sortBySeverity []*gosec.Issue func (s sortBySeverity) Len() int { return len(s) } @@ -15,6 +15,6 @@ func (s sortBySeverity) Less(i, j int) bool { return s[i].Severity > s[i].Severi 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 []*gas.Issue) { +func sortIssues(issues []*gosec.Issue) { sort.Sort(sortBySeverity(issues)) } diff --git a/cmd/gas/version.go b/cmd/gosec/version.go similarity index 100% rename from cmd/gas/version.go rename to cmd/gosec/version.go diff --git a/cmd/gasutil/tools.go b/cmd/gosecutil/tools.go similarity index 100% rename from cmd/gasutil/tools.go rename to cmd/gosecutil/tools.go diff --git a/cmd/tlsconfig/header_template.go b/cmd/tlsconfig/header_template.go index 618221b..3593645 100644 --- a/cmd/tlsconfig/header_template.go +++ b/cmd/tlsconfig/header_template.go @@ -8,6 +8,6 @@ package {{.}} import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) `)) diff --git a/cmd/tlsconfig/rule_template.go b/cmd/tlsconfig/rule_template.go index bea9c39..952be29 100644 --- a/cmd/tlsconfig/rule_template.go +++ b/cmd/tlsconfig/rule_template.go @@ -5,9 +5,9 @@ import "text/template" var generatedRuleTmpl = template.Must(template.New("generated").Parse(` // New{{.Name}}TLSCheck creates a check for {{.Name}} TLS ciphers // DO NOT EDIT - generated by tlsconfig tool -func New{{.Name}}TLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func New{{.Name}}TLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gas.MetaData{ID: id}, + MetaData: gosec.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: {{ .MinVersion }}, MaxVersion: {{ .MaxVersion }}, diff --git a/config.go b/config.go index 09b97d3..a19937f 100644 --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -package gas +package gosec import ( "bytes" @@ -10,7 +10,7 @@ import ( const ( // Globals are applicable to all rules and used for general - // configuration settings for gas. + // configuration settings for gosec. Globals = "global" ) diff --git a/config_test.go b/config_test.go index a1ff0f5..724717a 100644 --- a/config_test.go +++ b/config_test.go @@ -1,17 +1,17 @@ -package gas_test +package gosec_test import ( "bytes" - "github.com/GoASTScanner/gas" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" ) var _ = Describe("Configuration", func() { - var configuration gas.Config + var configuration gosec.Config BeforeEach(func() { - configuration = gas.NewConfig() + configuration = gosec.NewConfig() }) Context("when loading from disk", func() { diff --git a/gas_suite_test.go b/gosec_suite_test.go similarity index 58% rename from gas_suite_test.go rename to gosec_suite_test.go index 649d89a..7475c35 100644 --- a/gas_suite_test.go +++ b/gosec_suite_test.go @@ -1,4 +1,4 @@ -package gas_test +package gosec_test import ( . "github.com/onsi/ginkgo" @@ -7,7 +7,7 @@ import ( "testing" ) -func TestGas(t *testing.T) { +func TestGosec(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Gas Suite") + RunSpecs(t, "gosec Suite") } diff --git a/helpers.go b/helpers.go index 8bd1f5c..abe4c15 100644 --- a/helpers.go +++ b/helpers.go @@ -12,14 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import ( + "errors" "fmt" "go/ast" "go/token" "go/types" + "os" + "os/user" + "path/filepath" + "runtime" "strconv" + "strings" ) // MatchCallByPackage ensures that the specified package is imported, @@ -193,3 +199,60 @@ func GetLocation(n ast.Node, ctx *Context) (string, int) { fobj := ctx.FileSet.File(n.Pos()) return fobj.Name(), fobj.Line(n.Pos()) } + +// Gopath returns all GOPATHs +func Gopath() []string { + defaultGoPath := runtime.GOROOT() + if u, err := user.Current(); err == nil { + defaultGoPath = filepath.Join(u.HomeDir, "go") + } + path := Getenv("GOPATH", defaultGoPath) + paths := strings.Split(path, string(os.PathListSeparator)) + for idx, path := range paths { + if abs, err := filepath.Abs(path); err == nil { + paths[idx] = abs + } + } + return paths +} + +// Getenv returns the values of the environment variable, otherwise +//returns the default if variable is not set +func Getenv(key, userDefault string) string { + if val := os.Getenv(key); val != "" { + return val + } + return userDefault +} + +// GetPkgRelativePath returns the Go relative relative path derived +// form the given path +func GetPkgRelativePath(path string) (string, error) { + abspath, err := filepath.Abs(path) + if err != nil { + abspath = path + } + if strings.HasSuffix(abspath, ".go") { + abspath = filepath.Dir(abspath) + } + for _, base := range Gopath() { + projectRoot := filepath.FromSlash(fmt.Sprintf("%s/src/", base)) + if strings.HasPrefix(abspath, projectRoot) { + return strings.TrimPrefix(abspath, projectRoot), nil + } + } + return "", errors.New("no project relative path found") +} + +// GetPkgAbsPath returns the Go package absolute path derived from +// the given path +func GetPkgAbsPath(pkgPath string) (string, error) { + absPath, err := filepath.Abs(pkgPath) + if err != nil { + return "", err + } + if _, err := os.Stat(absPath); os.IsNotExist(err) { + return "", errors.New("no project absolute path found") + } + return absPath, nil +} diff --git a/helpers_test.go b/helpers_test.go index 5676707..2f683cb 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,4 +1,4 @@ -package gas_test +package gosec_test import ( . "github.com/onsi/ginkgo" diff --git a/import_tracker.go b/import_tracker.go index 0f948fb..b56b65a 100644 --- a/import_tracker.go +++ b/import_tracker.go @@ -10,7 +10,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import ( "go/ast" diff --git a/issue.go b/issue.go index 5ec39bc..40bfa3d 100644 --- a/issue.go +++ b/issue.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import ( "encoding/json" @@ -34,7 +34,7 @@ const ( High ) -// Issue is returnd by a GAS rule if it discovers an issue with the scanned code. +// Issue is returnd by a gosec rule if it discovers an issue with the scanned code. type Issue struct { Severity Score `json:"severity"` // issue severity (how problematic it is) Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) @@ -45,7 +45,7 @@ type Issue struct { Line string `json:"line"` // Line number in file } -// MetaData is embedded in all GAS rules. The Severity, Confidence and What message +// MetaData is embedded in all gosec rules. The Severity, Confidence and What message // will be passed tbhrough to reported issues. type MetaData struct { ID string diff --git a/issue_test.go b/issue_test.go index 1203697..68c4a5f 100644 --- a/issue_test.go +++ b/issue_test.go @@ -1,13 +1,13 @@ -package gas_test +package gosec_test import ( "go/ast" - "github.com/GoASTScanner/gas" - "github.com/GoASTScanner/gas/rules" - "github.com/GoASTScanner/gas/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" + "github.com/securego/gosec/rules" + "github.com/securego/gosec/testutils" ) var _ = Describe("Issue", func() { @@ -26,7 +26,7 @@ var _ = Describe("Issue", func() { pkg.AddFile("foo.go", source) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.BasicLit); ok { target = node return false @@ -37,7 +37,7 @@ var _ = Describe("Issue", func() { ast.Walk(v, ctx.Root) Expect(target).ShouldNot(BeNil()) - issue := gas.NewIssue(ctx, target, "TEST", "", gas.High, gas.High) + issue := gosec.NewIssue(ctx, target, "TEST", "", gosec.High, gosec.High) Expect(issue).ShouldNot(BeNil()) Expect(issue.Code).Should(MatchRegexp(`"bar"`)) Expect(issue.Line).Should(Equal("2")) @@ -58,7 +58,7 @@ var _ = Describe("Issue", func() { source := `package main import "os" func main(){` - source += "q := `SELECT * FROM table WHERE` + \n os.Args[1] + `= ?` // nolint: gas\n" + source += "q := `SELECT * FROM table WHERE` + \n os.Args[1] + `= ?` // nolint: gosec\n" source += `println(q)}` pkg := testutils.NewTestPackage() @@ -66,7 +66,7 @@ var _ = Describe("Issue", func() { pkg.AddFile("foo.go", source) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.BinaryExpr); ok { target = node } @@ -77,7 +77,7 @@ var _ = Describe("Issue", func() { Expect(target).ShouldNot(BeNil()) // Use SQL rule to check binary expr - cfg := gas.NewConfig() + cfg := gosec.NewConfig() rule, _ := rules.NewSQLStrConcat("TEST", cfg) issue, err := rule.Match(target, ctx) Expect(err).ShouldNot(HaveOccurred()) diff --git a/output/formatter.go b/output/formatter.go index 3d9d248..ee98de4 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -22,7 +22,7 @@ import ( "io" plainTemplate "text/template" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" "gopkg.in/yaml.v2" ) @@ -58,13 +58,13 @@ Summary: ` type reportInfo struct { - Issues []*gas.Issue - Stats *gas.Metrics + Issues []*gosec.Issue + Stats *gosec.Metrics } // CreateReport generates a report based for the supplied issues and metrics given // the specified format. The formats currently accepted are: json, csv, html and text. -func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.Metrics) error { +func CreateReport(w io.Writer, format string, issues []*gosec.Issue, metrics *gosec.Metrics) error { data := &reportInfo{ Issues: issues, Stats: metrics, @@ -150,7 +150,7 @@ func reportJUnitXML(w io.Writer, data *reportInfo) error { } func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error { - t, e := plainTemplate.New("gas").Parse(reportTemplate) + t, e := plainTemplate.New("gosec").Parse(reportTemplate) if e != nil { return e } @@ -159,7 +159,7 @@ func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *repor } func reportFromHTMLTemplate(w io.Writer, reportTemplate string, data *reportInfo) error { - t, e := htmlTemplate.New("gas").Parse(reportTemplate) + t, e := htmlTemplate.New("gosec").Parse(reportTemplate) if e != nil { return e } diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go index 2fd5c39..547d5b2 100644 --- a/output/junit_xml_format.go +++ b/output/junit_xml_format.go @@ -5,7 +5,7 @@ import ( htmlLib "html" "strconv" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type junitXMLReport struct { @@ -32,26 +32,26 @@ type failure struct { Text string `xml:",innerxml"` } -func generatePlaintext(issue *gas.Issue) string { +func generatePlaintext(issue *gosec.Issue) string { return "Results:\n" + "[" + issue.File + ":" + issue.Line + "] - " + issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + ", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + htmlLib.EscapeString(issue.Code) } -func groupDataByRules(data *reportInfo) map[string][]*gas.Issue { - groupedData := make(map[string][]*gas.Issue) +func groupDataByRules(data *reportInfo) map[string][]*gosec.Issue { + groupedData := make(map[string][]*gosec.Issue) for _, issue := range data.Issues { if _, ok := groupedData[issue.What]; ok { groupedData[issue.What] = append(groupedData[issue.What], issue) } else { - groupedData[issue.What] = []*gas.Issue{issue} + groupedData[issue.What] = []*gosec.Issue{issue} } } return groupedData } -func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport { +func createJUnitXMLStruct(groupedData map[string][]*gosec.Issue) junitXMLReport { var xmlReport junitXMLReport for what, issues := range groupedData { testsuite := testsuite{ diff --git a/resolve.go b/resolve.go index d7c6dce..b563e7d 100644 --- a/resolve.go +++ b/resolve.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import "go/ast" diff --git a/resolve_test.go b/resolve_test.go index f17a825..afc0316 100644 --- a/resolve_test.go +++ b/resolve_test.go @@ -1,12 +1,12 @@ -package gas_test +package gosec_test import ( "go/ast" - "github.com/GoASTScanner/gas" - "github.com/GoASTScanner/gas/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" + "github.com/securego/gosec/testutils" ) var _ = Describe("Resolve ast node to concrete value", func() { @@ -19,7 +19,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { pkg.AddFile("foo.go", `package main; const foo = "bar"; func main(){}`) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.BasicLit); ok { basicLiteral = node return false @@ -29,7 +29,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { v.Context = ctx ast.Walk(v, ctx.Root) Expect(basicLiteral).ShouldNot(BeNil()) - Expect(gas.TryResolve(basicLiteral, ctx)).Should(BeTrue()) + Expect(gosec.TryResolve(basicLiteral, ctx)).Should(BeTrue()) }) It("should successfully resolve identifier", func() { @@ -39,7 +39,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { pkg.AddFile("foo.go", `package main; var foo string = "bar"; func main(){}`) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.Ident); ok { ident = node return false @@ -49,7 +49,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { v.Context = ctx ast.Walk(v, ctx.Root) Expect(ident).ShouldNot(BeNil()) - Expect(gas.TryResolve(ident, ctx)).Should(BeTrue()) + Expect(gosec.TryResolve(ident, ctx)).Should(BeTrue()) }) It("should successfully resolve assign statement", func() { @@ -59,7 +59,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { pkg.AddFile("foo.go", `package main; const x = "bar"; func main(){ y := x; println(y) }`) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.AssignStmt); ok { if id, ok := node.Lhs[0].(*ast.Ident); ok && id.Name == "y" { assign = node @@ -70,7 +70,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { v.Context = ctx ast.Walk(v, ctx.Root) Expect(assign).ShouldNot(BeNil()) - Expect(gas.TryResolve(assign, ctx)).Should(BeTrue()) + Expect(gosec.TryResolve(assign, ctx)).Should(BeTrue()) }) It("should successfully resolve a binary statement", func() { @@ -80,7 +80,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { pkg.AddFile("foo.go", `package main; const (x = "bar"; y = "baz"); func main(){ z := x + y; println(z) }`) ctx := pkg.CreateContext("foo.go") v := testutils.NewMockVisitor() - v.Callback = func(n ast.Node, ctx *gas.Context) bool { + v.Callback = func(n ast.Node, ctx *gosec.Context) bool { if node, ok := n.(*ast.BinaryExpr); ok { target = node } @@ -89,7 +89,7 @@ var _ = Describe("Resolve ast node to concrete value", func() { v.Context = ctx ast.Walk(v, ctx.Root) Expect(target).ShouldNot(BeNil()) - Expect(gas.TryResolve(target, ctx)).Should(BeTrue()) + Expect(gosec.TryResolve(target, ctx)).Should(BeTrue()) }) // TODO: It should resolve call expressions diff --git a/rule.go b/rule.go index 95c6562..415c708 100644 --- a/rule.go +++ b/rule.go @@ -10,14 +10,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package gas +package gosec import ( "go/ast" "reflect" ) -// The Rule interface used by all rules supported by GAS. +// The Rule interface used by all rules supported by gosec. type Rule interface { ID() string Match(ast.Node, *Context) (*Issue, error) diff --git a/rule_test.go b/rule_test.go index 196e575..dbbf38f 100644 --- a/rule_test.go +++ b/rule_test.go @@ -1,25 +1,25 @@ -package gas_test +package gosec_test import ( "fmt" "go/ast" - "github.com/GoASTScanner/gas" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" ) type mockrule struct { - issue *gas.Issue + issue *gosec.Issue err error - callback func(n ast.Node, ctx *gas.Context) bool + callback func(n ast.Node, ctx *gosec.Context) bool } func (m *mockrule) ID() string { return "MOCK" } -func (m *mockrule) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { +func (m *mockrule) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { if m.callback(n, ctx) { return m.issue, nil } @@ -31,29 +31,29 @@ var _ = Describe("Rule", func() { Context("when using a ruleset", func() { var ( - ruleset gas.RuleSet - dummyErrorRule gas.Rule - dummyIssueRule gas.Rule + ruleset gosec.RuleSet + dummyErrorRule gosec.Rule + dummyIssueRule gosec.Rule ) JustBeforeEach(func() { - ruleset = gas.NewRuleSet() + ruleset = gosec.NewRuleSet() dummyErrorRule = &mockrule{ issue: nil, err: fmt.Errorf("An unexpected error occurred"), - callback: func(n ast.Node, ctx *gas.Context) bool { return false }, + callback: func(n ast.Node, ctx *gosec.Context) bool { return false }, } dummyIssueRule = &mockrule{ - issue: &gas.Issue{ - Severity: gas.High, - Confidence: gas.High, + issue: &gosec.Issue{ + Severity: gosec.High, + Confidence: gosec.High, What: `Some explanation of the thing`, File: "main.go", Code: `#include int main(){ puts("hello world"); }`, Line: "42", }, err: nil, - callback: func(n ast.Node, ctx *gas.Context) bool { return true }, + callback: func(n ast.Node, ctx *gosec.Context) bool { return true }, } }) It("should be possible to register a rule for multiple ast.Node", func() { diff --git a/rules/archive.go b/rules/archive.go new file mode 100644 index 0000000..1fa2b40 --- /dev/null +++ b/rules/archive.go @@ -0,0 +1,60 @@ +package rules + +import ( + "go/ast" + "go/types" + + "github.com/securego/gosec" +) + +type archive struct { + gosec.MetaData + calls gosec.CallList + argType string +} + +func (a *archive) ID() string { + return a.MetaData.ID +} + +// Match inspects AST nodes to determine if the filepath.Joins uses any argument derived from type zip.File +func (a *archive) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { + if node := a.calls.ContainsCallExpr(n, c); node != nil { + for _, arg := range node.Args { + var argType types.Type + if selector, ok := arg.(*ast.SelectorExpr); ok { + argType = c.Info.TypeOf(selector.X) + } else if ident, ok := arg.(*ast.Ident); ok { + if ident.Obj != nil && ident.Obj.Kind == ast.Var { + decl := ident.Obj.Decl + if assign, ok := decl.(*ast.AssignStmt); ok { + if selector, ok := assign.Rhs[0].(*ast.SelectorExpr); ok { + argType = c.Info.TypeOf(selector.X) + } + } + } + } + + if argType != nil && argType.String() == a.argType { + return gosec.NewIssue(c, n, a.ID(), a.What, a.Severity, a.Confidence), nil + } + } + } + return nil, nil +} + +// NewArchive creates a new rule which detects the file traversal when extracting zip archives +func NewArchive(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + calls := gosec.NewCallList() + calls.Add("path/filepath", "Join") + return &archive{ + calls: calls, + argType: "*archive/zip.File", + MetaData: gosec.MetaData{ + ID: id, + Severity: gosec.Medium, + Confidence: gosec.High, + What: "File traversal when extracting zip archive", + }, + }, []ast.Node{(*ast.CallExpr)(nil)} +} diff --git a/rules/big.go b/rules/big.go index f4aeb3e..8c45a53 100644 --- a/rules/big.go +++ b/rules/big.go @@ -17,11 +17,11 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type usingBigExp struct { - gas.MetaData + gosec.MetaData pkg string calls []string } @@ -30,23 +30,23 @@ func (r *usingBigExp) ID() string { return r.MetaData.ID } -func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { - if _, matched := gas.MatchCallByType(n, c, r.pkg, r.calls...); matched { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil +func (r *usingBigExp) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { + if _, matched := gosec.MatchCallByType(n, c, r.pkg, r.calls...); matched { + return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil } return nil, nil } // NewUsingBigExp detects issues with modulus == 0 for Bignum -func NewUsingBigExp(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewUsingBigExp(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &usingBigExp{ pkg: "*math/big.Int", calls: []string{"Exp"}, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, What: "Use of math/big.Int.Exp function should be audited for modulus == 0", - Severity: gas.Low, - Confidence: gas.High, + Severity: gosec.Low, + Confidence: gosec.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/bind.go b/rules/bind.go index 1cd8bf2..a7d599b 100644 --- a/rules/bind.go +++ b/rules/bind.go @@ -18,13 +18,13 @@ import ( "go/ast" "regexp" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) // Looks for net.Listen("0.0.0.0") or net.Listen(":8080") type bindsToAllNetworkInterfaces struct { - gas.MetaData - calls gas.CallList + gosec.MetaData + calls gosec.CallList pattern *regexp.Regexp } @@ -32,14 +32,14 @@ func (r *bindsToAllNetworkInterfaces) ID() string { return r.MetaData.ID } -func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { callExpr := r.calls.ContainsCallExpr(n, c) if callExpr == nil { return nil, nil } - if arg, err := gas.GetString(callExpr.Args[1]); err == nil { + if arg, err := gosec.GetString(callExpr.Args[1]); err == nil { if r.pattern.MatchString(arg) { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } return nil, nil @@ -47,17 +47,17 @@ func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Is // NewBindsToAllNetworkInterfaces detects socket connections that are setup to // listen on all network interfaces. -func NewBindsToAllNetworkInterfaces(id string, conf gas.Config) (gas.Rule, []ast.Node) { - calls := gas.NewCallList() +func NewBindsToAllNetworkInterfaces(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + calls := gosec.NewCallList() calls.Add("net", "Listen") calls.Add("crypto/tls", "Listen") return &bindsToAllNetworkInterfaces{ calls: calls, pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`), - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: "Binds to all network interfaces", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/blacklist.go b/rules/blacklist.go index 92d8ed4..74a769c 100644 --- a/rules/blacklist.go +++ b/rules/blacklist.go @@ -18,11 +18,11 @@ import ( "go/ast" "strings" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type blacklistedImport struct { - gas.MetaData + gosec.MetaData Blacklisted map[string]string } @@ -36,10 +36,10 @@ func (r *blacklistedImport) ID() string { return r.MetaData.ID } -func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (r *blacklistedImport) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if node, ok := n.(*ast.ImportSpec); ok { if description, ok := r.Blacklisted[unquote(node.Path.Value)]; ok { - return gas.NewIssue(c, node, r.ID(), description, r.Severity, r.Confidence), nil + return gosec.NewIssue(c, node, r.ID(), description, r.Severity, r.Confidence), nil } } return nil, nil @@ -47,40 +47,40 @@ func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error // NewBlacklistedImports reports when a blacklisted import is being used. // Typically when a deprecated technology is being used. -func NewBlacklistedImports(id string, conf gas.Config, blacklist map[string]string) (gas.Rule, []ast.Node) { +func NewBlacklistedImports(id string, conf gosec.Config, blacklist map[string]string) (gosec.Rule, []ast.Node) { return &blacklistedImport{ - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, }, Blacklisted: blacklist, }, []ast.Node{(*ast.ImportSpec)(nil)} } // NewBlacklistedImportMD5 fails if MD5 is imported -func NewBlacklistedImportMD5(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewBlacklistedImportMD5(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return NewBlacklistedImports(id, conf, map[string]string{ "crypto/md5": "Blacklisted import crypto/md5: weak cryptographic primitive", }) } // NewBlacklistedImportDES fails if DES is imported -func NewBlacklistedImportDES(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewBlacklistedImportDES(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return NewBlacklistedImports(id, conf, map[string]string{ "crypto/des": "Blacklisted import crypto/des: weak cryptographic primitive", }) } // NewBlacklistedImportRC4 fails if DES is imported -func NewBlacklistedImportRC4(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewBlacklistedImportRC4(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return NewBlacklistedImports(id, conf, map[string]string{ "crypto/rc4": "Blacklisted import crypto/rc4: weak cryptographic primitive", }) } // NewBlacklistedImportCGI fails if CGI is imported -func NewBlacklistedImportCGI(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewBlacklistedImportCGI(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return NewBlacklistedImports(id, conf, map[string]string{ "net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)", }) diff --git a/rules/errors.go b/rules/errors.go index 03ededf..5aea57d 100644 --- a/rules/errors.go +++ b/rules/errors.go @@ -18,19 +18,19 @@ import ( "go/ast" "go/types" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type noErrorCheck struct { - gas.MetaData - whitelist gas.CallList + gosec.MetaData + whitelist gosec.CallList } func (r *noErrorCheck) ID() string { return r.MetaData.ID } -func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int { +func returnsError(callExpr *ast.CallExpr, ctx *gosec.Context) int { if tv := ctx.Info.TypeOf(callExpr); tv != nil { switch t := tv.(type) { case *types.Tuple: @@ -49,7 +49,7 @@ func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int { return -1 } -func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { +func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { switch stmt := n.(type) { case *ast.AssignStmt: for _, expr := range stmt.Rhs { @@ -59,7 +59,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { return nil, nil } if id, ok := stmt.Lhs[pos].(*ast.Ident); ok && id.Name == "_" { - return gas.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -67,7 +67,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil { pos := returnsError(callExpr, ctx) if pos >= 0 { - return gas.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil + return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -75,10 +75,10 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { } // NewNoErrorCheck detects if the returned error is unchecked -func NewNoErrorCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { // TODO(gm) Come up with sensible defaults here. Or flip it to use a // black list instead. - whitelist := gas.NewCallList() + whitelist := gosec.NewCallList() whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString") whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln") whitelist.Add("io.PipeWriter", "CloseWithError") @@ -91,10 +91,10 @@ func NewNoErrorCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { } } return &noErrorCheck{ - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Low, - Confidence: gas.High, + Severity: gosec.Low, + Confidence: gosec.High, What: "Errors unhandled.", }, whitelist: whitelist, diff --git a/rules/fileperms.go b/rules/fileperms.go index 6276c85..8e94369 100644 --- a/rules/fileperms.go +++ b/rules/fileperms.go @@ -19,11 +19,11 @@ import ( "go/ast" "strconv" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type filePermissions struct { - gas.MetaData + gosec.MetaData mode int64 pkg string calls []string @@ -50,11 +50,11 @@ func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMod return mode } -func (r *filePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { - if callexpr, matched := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matched { +func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { + if callexpr, matched := gosec.MatchCallByPackage(n, c, r.pkg, r.calls...); matched { modeArg := callexpr.Args[len(callexpr.Args)-1] - if mode, err := gas.GetInt(modeArg); err == nil && mode > r.mode { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + if mode, err := gosec.GetInt(modeArg); err == nil && mode > r.mode { + return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } return nil, nil @@ -62,16 +62,16 @@ func (r *filePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) // NewFilePerms creates a rule to detect file creation with a more permissive than configured // permission mask. -func NewFilePerms(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewFilePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode := getConfiguredMode(conf, "G302", 0600) return &filePermissions{ mode: mode, pkg: "os", calls: []string{"OpenFile", "Chmod"}, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: fmt.Sprintf("Expect file permissions to be %#o or less", mode), }, }, []ast.Node{(*ast.CallExpr)(nil)} @@ -79,16 +79,16 @@ func NewFilePerms(id string, conf gas.Config) (gas.Rule, []ast.Node) { // NewMkdirPerms creates a rule to detect directory creation with more permissive than // configured permission mask. -func NewMkdirPerms(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewMkdirPerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode := getConfiguredMode(conf, "G301", 0750) return &filePermissions{ mode: mode, pkg: "os", calls: []string{"Mkdir", "MkdirAll"}, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.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 0040710..17a5cdf 100644 --- a/rules/hardcoded_credentials.go +++ b/rules/hardcoded_credentials.go @@ -19,12 +19,12 @@ import ( "regexp" "strconv" - "github.com/GoASTScanner/gas" - "github.com/nbutton23/zxcvbn-go" + zxcvbn "github.com/nbutton23/zxcvbn-go" + "github.com/securego/gosec" ) type credentials struct { - gas.MetaData + gosec.MetaData pattern *regexp.Regexp entropyThreshold float64 perCharThreshold float64 @@ -52,7 +52,7 @@ func (r *credentials) isHighEntropyString(str string) bool { entropyPerChar >= r.perCharThreshold)) } -func (r *credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { +func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { switch node := n.(type) { case *ast.AssignStmt: return r.matchAssign(node, ctx) @@ -62,14 +62,14 @@ func (r *credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) { return nil, nil } -func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*gas.Issue, error) { +func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*gosec.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 := gas.GetString(e); err == nil { + if val, err := gosec.GetString(e); err == nil { if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) { - return gas.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil + return gosec.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -79,16 +79,16 @@ func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*ga return nil, nil } -func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gas.Context) (*gas.Issue, error) { +func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Context) (*gosec.Issue, error) { for index, ident := range valueSpec.Names { if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil { // const foo, bar = "same value" if len(valueSpec.Values) <= index { index = len(valueSpec.Values) - 1 } - if val, err := gas.GetString(valueSpec.Values[index]); err == nil { + if val, err := gosec.GetString(valueSpec.Values[index]); err == nil { if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) { - return gas.NewIssue(ctx, valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil + return gosec.NewIssue(ctx, valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -98,7 +98,7 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gas.Context) // NewHardcodedCredentials attempts to find high entropy string constants being // assigned to variables that appear to be related to credentials. -func NewHardcodedCredentials(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { pattern := `(?i)passwd|pass|password|pwd|secret|token` entropyThreshold := 80.0 perCharThreshold := 3.0 @@ -137,11 +137,11 @@ func NewHardcodedCredentials(id string, conf gas.Config) (gas.Rule, []ast.Node) perCharThreshold: perCharThreshold, ignoreEntropy: ignoreEntropy, truncate: truncateString, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, What: "Potential hardcoded credentials", - Confidence: gas.Low, - Severity: gas.High, + Confidence: gosec.Low, + Severity: gosec.High, }, }, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil)} } diff --git a/rules/rand.go b/rules/rand.go index c85f10f..a2bdabe 100644 --- a/rules/rand.go +++ b/rules/rand.go @@ -17,11 +17,11 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type weakRand struct { - gas.MetaData + gosec.MetaData funcNames []string packagePath string } @@ -30,10 +30,10 @@ func (w *weakRand) ID() string { return w.MetaData.ID } -func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (w *weakRand) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { for _, funcName := range w.funcNames { - if _, matched := gas.MatchCallByPackage(n, c, w.packagePath, funcName); matched { - return gas.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil + if _, matched := gosec.MatchCallByPackage(n, c, w.packagePath, funcName); matched { + return gosec.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil } } @@ -41,14 +41,14 @@ func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { } // NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure -func NewWeakRandCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewWeakRandCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &weakRand{ funcNames: []string{"Read", "Int"}, packagePath: "math/rand", - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.High, - Confidence: gas.Medium, + Severity: gosec.High, + Confidence: gosec.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 d6c2186..61e1c85 100644 --- a/rules/readfile.go +++ b/rules/readfile.go @@ -18,12 +18,12 @@ import ( "go/ast" "go/types" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type readfile struct { - gas.MetaData - gas.CallList + gosec.MetaData + gosec.CallList } // ID returns the identifier for this rule @@ -32,13 +32,13 @@ func (r *readfile) ID() string { } // Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile` -func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if node := r.ContainsCallExpr(n, c); node != nil { for _, arg := range node.Args { if ident, ok := arg.(*ast.Ident); ok { obj := c.Info.ObjectOf(ident) - if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) { + return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } } @@ -47,14 +47,14 @@ func (r *readfile) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { } // NewReadFile detects cases where we read files -func NewReadFile(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewReadFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { rule := &readfile{ - CallList: gas.NewCallList(), - MetaData: gas.MetaData{ + CallList: gosec.NewCallList(), + MetaData: gosec.MetaData{ ID: id, What: "Potential file inclusion via variable", - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, }, } rule.Add("io/ioutil", "ReadFile") diff --git a/rules/rsa.go b/rules/rsa.go index 99c6e82..4a42905 100644 --- a/rules/rsa.go +++ b/rules/rsa.go @@ -18,12 +18,12 @@ import ( "fmt" "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type weakKeyStrength struct { - gas.MetaData - calls gas.CallList + gosec.MetaData + calls gosec.CallList bits int } @@ -31,27 +31,27 @@ func (w *weakKeyStrength) ID() string { return w.MetaData.ID } -func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (w *weakKeyStrength) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil { - if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) { - return gas.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), 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 nil, nil } // NewWeakKeyStrength builds a rule that detects RSA keys < 2048 bits -func NewWeakKeyStrength(id string, conf gas.Config) (gas.Rule, []ast.Node) { - calls := gas.NewCallList() +func NewWeakKeyStrength(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + calls := gosec.NewCallList() calls.Add("crypto/rsa", "GenerateKey") bits := 2048 return &weakKeyStrength{ calls: calls, bits: bits, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: fmt.Sprintf("RSA keys should be at least %d bits", bits), }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/rulelist.go b/rules/rulelist.go index f6f21af..16cb273 100644 --- a/rules/rulelist.go +++ b/rules/rulelist.go @@ -14,24 +14,22 @@ package rules -import ( - "github.com/GoASTScanner/gas" -) +import "github.com/securego/gosec" // RuleDefinition contains the description of a rule and a mechanism to // create it. type RuleDefinition struct { ID string Description string - Create gas.RuleBuilder + Create gosec.RuleBuilder } // RuleList is a mapping of rule ID's to rule definitions type RuleList map[string]RuleDefinition // Builders returns all the create methods for a given rule list -func (rl RuleList) Builders() map[string]gas.RuleBuilder { - builders := make(map[string]gas.RuleBuilder) +func (rl RuleList) Builders() map[string]gosec.RuleBuilder { + builders := make(map[string]gosec.RuleBuilder) for _, def := range rl { builders[def.ID] = def.Create } @@ -79,6 +77,7 @@ func Generate(filters ...RuleFilter) RuleList { {"G302", "Poor file permisions used when creation file or using chmod", NewFilePerms}, {"G303", "Creating tempfile using a predictable path", NewBadTempFile}, {"G304", "File path provided as taint input", NewReadFile}, + {"G305", "File path traversal when extracting zip archive", NewArchive}, // crypto {"G401", "Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography}, diff --git a/rules/rules_test.go b/rules/rules_test.go index 78a2619..958932f 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -4,28 +4,27 @@ import ( "fmt" "log" - "github.com/GoASTScanner/gas" - - "github.com/GoASTScanner/gas/rules" - "github.com/GoASTScanner/gas/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/securego/gosec" + "github.com/securego/gosec/rules" + "github.com/securego/gosec/testutils" ) -var _ = Describe("gas rules", func() { +var _ = Describe("gosec rules", func() { var ( logger *log.Logger - config gas.Config - analyzer *gas.Analyzer + config gosec.Config + analyzer *gosec.Analyzer runner func(string, []testutils.CodeSample) buildTags []string ) BeforeEach(func() { logger, _ = testutils.NewLogger() - config = gas.NewConfig() - analyzer = gas.NewAnalyzer(config, logger) + config = gosec.NewConfig() + analyzer = gosec.NewAnalyzer(config, logger) runner = func(rule string, samples []testutils.CodeSample) { analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, rule)).Builders()) for n, sample := range samples { @@ -103,6 +102,10 @@ var _ = Describe("gas rules", func() { runner("G304", testutils.SampleCodeG304) }) + It("should detect file path traversal when extracting zip archive", func() { + runner("G305", testutils.SampleCodeG305) + }) + It("should detect weak crypto algorithms", func() { runner("G401", testutils.SampleCodeG401) }) diff --git a/rules/sql.go b/rules/sql.go index a76f580..655f4b0 100644 --- a/rules/sql.go +++ b/rules/sql.go @@ -18,11 +18,11 @@ import ( "go/ast" "regexp" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type sqlStatement struct { - gas.MetaData + gosec.MetaData // Contains a list of patterns which must all match for the rule to match. patterns []*regexp.Regexp @@ -59,10 +59,10 @@ func (s *sqlStrConcat) checkObject(n *ast.Ident) bool { } // Look for "SELECT * FROM table WHERE " + " ' OR 1=1" -func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (s *sqlStrConcat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if node, ok := n.(*ast.BinaryExpr); ok { if start, ok := node.X.(*ast.BasicLit); ok { - if str, e := gas.GetString(start); e == nil { + if str, e := gosec.GetString(start); e == nil { if !s.MatchPatterns(str) { return nil, nil } @@ -72,7 +72,7 @@ func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second) { return nil, nil } - return gas.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil + return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil } } } @@ -80,16 +80,16 @@ func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { } // NewSQLStrConcat looks for cases where we are building SQL strings via concatenation -func NewSQLStrConcat(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewSQLStrConcat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &sqlStrConcat{ sqlStatement: sqlStatement{ patterns: []*regexp.Regexp{ regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `), }, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: "SQL string concatenation", }, }, @@ -98,34 +98,34 @@ func NewSQLStrConcat(id string, conf gas.Config) (gas.Rule, []ast.Node) { type sqlStrFormat struct { sqlStatement - calls gas.CallList + calls gosec.CallList } // Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)" -func (s *sqlStrFormat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { // TODO(gm) improve confidence if database/sql is being used if node := s.calls.ContainsCallExpr(n, c); node != nil { - if arg, e := gas.GetString(node.Args[0]); s.MatchPatterns(arg) && e == nil { - return gas.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil + if arg, e := gosec.GetString(node.Args[0]); s.MatchPatterns(arg) && e == nil { + return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil } } return nil, nil } // NewSQLStrFormat looks for cases where we're building SQL query strings using format strings -func NewSQLStrFormat(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { rule := &sqlStrFormat{ - calls: gas.NewCallList(), + calls: gosec.NewCallList(), sqlStatement: sqlStatement{ patterns: []*regexp.Regexp{ regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "), regexp.MustCompile("%[^bdoxXfFp]"), }, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: "SQL string formatting", }, }, diff --git a/rules/ssh.go b/rules/ssh.go index f4f18cc..7496b5f 100644 --- a/rules/ssh.go +++ b/rules/ssh.go @@ -3,11 +3,11 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type sshHostKey struct { - gas.MetaData + gosec.MetaData pkg string calls []string } @@ -16,23 +16,23 @@ func (r *sshHostKey) ID() string { return r.MetaData.ID } -func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { - if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil +func (r *sshHostKey) Match(n ast.Node, c *gosec.Context) (gi *gosec.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 nil, nil } // NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback. -func NewSSHHostKey(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewSSHHostKey(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &sshHostKey{ pkg: "golang.org/x/crypto/ssh", calls: []string{"InsecureIgnoreHostKey"}, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, What: "Use of ssh InsecureIgnoreHostKey should be audited", - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/subproc.go b/rules/subproc.go index b665f88..80a3464 100644 --- a/rules/subproc.go +++ b/rules/subproc.go @@ -18,12 +18,12 @@ import ( "go/ast" "go/types" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type subprocess struct { - gas.MetaData - gas.CallList + gosec.MetaData + gosec.CallList } func (r *subprocess) ID() string { @@ -39,24 +39,24 @@ func (r *subprocess) ID() string { // is unsafe. For example: // // syscall.Exec("echo", "foobar" + tainted) -func (r *subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if node := r.ContainsCallExpr(n, c); node != nil { for _, arg := range node.Args { if ident, ok := arg.(*ast.Ident); ok { obj := c.Info.ObjectOf(ident) - if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) { - return gas.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gas.Medium, gas.High), nil + if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) { + return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gosec.Medium, gosec.High), nil } } } - return gas.NewIssue(c, n, r.ID(), "Subprocess launching should be audited", gas.Low, gas.High), nil + return gosec.NewIssue(c, n, r.ID(), "Subprocess launching should be audited", gosec.Low, gosec.High), nil } return nil, nil } // NewSubproc detects cases where we are forking out to an external process -func NewSubproc(id string, conf gas.Config) (gas.Rule, []ast.Node) { - rule := &subprocess{gas.MetaData{ID: id}, gas.NewCallList()} +func NewSubproc(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + rule := &subprocess{gosec.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 664f774..6963404 100644 --- a/rules/tempfiles.go +++ b/rules/tempfiles.go @@ -18,12 +18,12 @@ import ( "go/ast" "regexp" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type badTempFile struct { - gas.MetaData - calls gas.CallList + gosec.MetaData + calls gosec.CallList args *regexp.Regexp } @@ -31,27 +31,27 @@ func (t *badTempFile) ID() string { return t.MetaData.ID } -func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { +func (t *badTempFile) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) { if node := t.calls.ContainsCallExpr(n, c); node != nil { - if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil { - return gas.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil + if arg, e := gosec.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil { + return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil } } return nil, nil } // NewBadTempFile detects direct writes to predictable path in temporary directory -func NewBadTempFile(id string, conf gas.Config) (gas.Rule, []ast.Node) { - calls := gas.NewCallList() +func NewBadTempFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + calls := gosec.NewCallList() calls.Add("io/ioutil", "WriteFile") calls.Add("os", "Create") return &badTempFile{ calls: calls, args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`), - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.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 66c37d6..30a964b 100644 --- a/rules/templates.go +++ b/rules/templates.go @@ -17,23 +17,23 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type templateCheck struct { - gas.MetaData - calls gas.CallList + gosec.MetaData + calls gosec.CallList } func (t *templateCheck) ID() string { return t.MetaData.ID } -func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { if node := t.calls.ContainsCallExpr(n, c); node != nil { for _, arg := range node.Args { if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe - return gas.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil + return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil } } } @@ -42,19 +42,19 @@ func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { // NewTemplateCheck constructs the template check rule. This rule is used to // find use of tempaltes where HTML/JS escaping is not being used -func NewTemplateCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewTemplateCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { - calls := gas.NewCallList() + calls := gosec.NewCallList() calls.Add("html/template", "HTML") calls.Add("html/template", "HTMLAttr") calls.Add("html/template", "JS") calls.Add("html/template", "URL") return &templateCheck{ calls: calls, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.Low, + Severity: gosec.Medium, + Confidence: gosec.Low, What: "this method will not auto-escape HTML. Verify data is well formed.", }, }, []ast.Node{(*ast.CallExpr)(nil)} diff --git a/rules/tls.go b/rules/tls.go index c437930..d4b7fa2 100644 --- a/rules/tls.go +++ b/rules/tls.go @@ -20,11 +20,11 @@ import ( "fmt" "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type insecureConfigTLS struct { - gas.MetaData + gosec.MetaData MinVersion int16 MaxVersion int16 requiredType string @@ -44,14 +44,14 @@ func stringInSlice(a string, list []string) bool { return false } -func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gas.Context) *gas.Issue { +func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gosec.Context) *gosec.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 gas.NewIssue(c, ident, t.ID(), err, gas.High, gas.High) + return gosec.NewIssue(c, ident, t.ID(), err, gosec.High, gosec.High) } } } @@ -59,46 +59,46 @@ func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gas.Context) * return nil } -func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gas.Context) *gas.Issue { +func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gosec.Context) *gosec.Issue { if ident, ok := n.Key.(*ast.Ident); ok { switch ident.Name { case "InsecureSkipVerify": if node, ok := n.Value.(*ast.Ident); ok { if node.Name != "false" { - return gas.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify set true.", gas.High, gas.High) + return gosec.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify set true.", gosec.High, gosec.High) } } else { // TODO(tk): symbol tab look up to get the actual value - return gas.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify may be true.", gas.High, gas.Low) + return gosec.NewIssue(c, n, t.ID(), "TLS InsecureSkipVerify may be true.", gosec.High, gosec.Low) } case "PreferServerCipherSuites": if node, ok := n.Value.(*ast.Ident); ok { if node.Name == "false" { - return gas.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites set false.", gas.Medium, gas.High) + return gosec.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites set false.", gosec.Medium, gosec.High) } } else { // TODO(tk): symbol tab look up to get the actual value - return gas.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites may be false.", gas.Medium, gas.Low) + return gosec.NewIssue(c, n, t.ID(), "TLS PreferServerCipherSuites may be false.", gosec.Medium, gosec.Low) } case "MinVersion": - if ival, ierr := gas.GetInt(n.Value); ierr == nil { + if ival, ierr := gosec.GetInt(n.Value); ierr == nil { if (int16)(ival) < t.MinVersion { - return gas.NewIssue(c, n, t.ID(), "TLS MinVersion too low.", gas.High, gas.High) + return gosec.NewIssue(c, n, t.ID(), "TLS MinVersion too low.", gosec.High, gosec.High) } // TODO(tk): symbol tab look up to get the actual value - return gas.NewIssue(c, n, t.ID(), "TLS MinVersion may be too low.", gas.High, gas.Low) + return gosec.NewIssue(c, n, t.ID(), "TLS MinVersion may be too low.", gosec.High, gosec.Low) } case "MaxVersion": - if ival, ierr := gas.GetInt(n.Value); ierr == nil { + if ival, ierr := gosec.GetInt(n.Value); ierr == nil { if (int16)(ival) < t.MaxVersion { - return gas.NewIssue(c, n, t.ID(), "TLS MaxVersion too low.", gas.High, gas.High) + return gosec.NewIssue(c, n, t.ID(), "TLS MaxVersion too low.", gosec.High, gosec.High) } // TODO(tk): symbol tab look up to get the actual value - return gas.NewIssue(c, n, t.ID(), "TLS MaxVersion may be too low.", gas.High, gas.Low) + return gosec.NewIssue(c, n, t.ID(), "TLS MaxVersion may be too low.", gosec.High, gosec.Low) } case "CipherSuites": @@ -112,7 +112,7 @@ func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gas.Contex return nil } -func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (t *insecureConfigTLS) Match(n ast.Node, c *gosec.Context) (*gosec.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 7242513..a629917 100644 --- a/rules/tls_config.go +++ b/rules/tls_config.go @@ -3,14 +3,14 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) // NewModernTLSCheck creates a check for Modern TLS ciphers // DO NOT EDIT - generated by tlsconfig tool -func NewModernTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewModernTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gas.MetaData{ID: id}, + MetaData: gosec.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0303, MaxVersion: 0x0303, @@ -31,9 +31,9 @@ func NewModernTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { // NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers // DO NOT EDIT - generated by tlsconfig tool -func NewIntermediateTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewIntermediateTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gas.MetaData{ID: id}, + MetaData: gosec.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0301, MaxVersion: 0x0303, @@ -74,9 +74,9 @@ func NewIntermediateTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) // NewOldTLSCheck creates a check for Old TLS ciphers // DO NOT EDIT - generated by tlsconfig tool -func NewOldTLSCheck(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewOldTLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &insecureConfigTLS{ - MetaData: gas.MetaData{ID: id}, + MetaData: gosec.MetaData{ID: id}, requiredType: "crypto/tls.Config", MinVersion: 0x0300, MaxVersion: 0x0303, diff --git a/rules/unsafe.go b/rules/unsafe.go index 8742dbc..f4a38be 100644 --- a/rules/unsafe.go +++ b/rules/unsafe.go @@ -17,11 +17,11 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type usingUnsafe struct { - gas.MetaData + gosec.MetaData pkg string calls []string } @@ -30,24 +30,24 @@ func (r *usingUnsafe) ID() string { return r.MetaData.ID } -func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) { - if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil +func (r *usingUnsafe) Match(n ast.Node, c *gosec.Context) (gi *gosec.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 nil, nil } // NewUsingUnsafe rule detects the use of the unsafe package. This is only // really useful for auditing purposes. -func NewUsingUnsafe(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewUsingUnsafe(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { return &usingUnsafe{ pkg: "unsafe", calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"}, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, What: "Use of unsafe calls should be audited", - Severity: gas.Low, - Confidence: gas.High, + Severity: gosec.Low, + Confidence: gosec.High, }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/rules/weakcrypto.go b/rules/weakcrypto.go index 2fb9686..db1ada7 100644 --- a/rules/weakcrypto.go +++ b/rules/weakcrypto.go @@ -17,11 +17,11 @@ package rules import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) type usesWeakCryptography struct { - gas.MetaData + gosec.MetaData blacklist map[string][]string } @@ -29,27 +29,27 @@ func (r *usesWeakCryptography) ID() string { return r.MetaData.ID } -func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { +func (r *usesWeakCryptography) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { for pkg, funcs := range r.blacklist { - if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched { - return gas.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil + if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { + return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil } } return nil, nil } // NewUsesWeakCryptography detects uses of des.* md5.* or rc4.* -func NewUsesWeakCryptography(id string, conf gas.Config) (gas.Rule, []ast.Node) { +func NewUsesWeakCryptography(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { calls := make(map[string][]string) calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"} calls["crypto/md5"] = []string{"New", "Sum"} calls["crypto/rc4"] = []string{"NewCipher"} rule := &usesWeakCryptography{ blacklist: calls, - MetaData: gas.MetaData{ + MetaData: gosec.MetaData{ ID: id, - Severity: gas.Medium, - Confidence: gas.High, + Severity: gosec.Medium, + Confidence: gosec.High, What: "Use of weak cryptographic primitive", }, } diff --git a/testutils/pkg.go b/testutils/pkg.go index a7dbdb0..ee85ac7 100644 --- a/testutils/pkg.go +++ b/testutils/pkg.go @@ -10,7 +10,7 @@ import ( "path" "strings" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" "golang.org/x/tools/go/loader" ) @@ -33,7 +33,7 @@ type TestPackage struct { func NewTestPackage() *TestPackage { // Files must exist in $GOPATH sourceDir := path.Join(os.Getenv("GOPATH"), "src") - workingDir, err := ioutil.TempDir(sourceDir, "gas_test") + workingDir, err := ioutil.TempDir(sourceDir, "gosecs_test") if err != nil { return nil } @@ -97,7 +97,7 @@ func (p *TestPackage) Build() error { } // CreateContext builds a context out of supplied package context -func (p *TestPackage) CreateContext(filename string) *gas.Context { +func (p *TestPackage) CreateContext(filename string) *gosec.Context { if err := p.Build(); err != nil { log.Fatal(err) return nil @@ -109,13 +109,13 @@ func (p *TestPackage) CreateContext(filename string) *gas.Context { strip := fmt.Sprintf("%s%c", p.Path, os.PathSeparator) pkgFile = strings.TrimPrefix(pkgFile, strip) if pkgFile == filename { - ctx := &gas.Context{ + ctx := &gosec.Context{ FileSet: p.build.program.Fset, Root: file, - Config: gas.NewConfig(), + Config: gosec.NewConfig(), Info: &pkg.Info, Pkg: pkg.Pkg, - Imports: gas.NewImportTracker(), + Imports: gosec.NewImportTracker(), } ctx.Imports.TrackPackages(ctx.Pkg.Imports()...) return ctx diff --git a/testutils/source.go b/testutils/source.go index 4a1448a..00a9f2c 100644 --- a/testutils/source.go +++ b/testutils/source.go @@ -515,6 +515,100 @@ func main() { log.Fatal(http.ListenAndServe(":3000", nil)) }`, 1}} + // SampleCodeG305 - File path traversal when extracting zip archives + SampleCodeG305 = []CodeSample{{` +package unzip + +import ( + "archive/zip" + "io" + "os" + "path/filepath" +) + +func unzip(archive, target string) error { + reader, err := zip.OpenReader(archive) + if err != nil { + return err + } + + if err := os.MkdirAll(target, 0750); err != nil { + return err + } + + for _, file := range reader.File { + path := filepath.Join(target, file.Name) + if file.FileInfo().IsDir() { + os.MkdirAll(path, file.Mode()) // #nosec + continue + } + + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, fileReader); err != nil { + return err + } + } + + return nil +}`, 1}, {` +package unzip + +import ( + "archive/zip" + "io" + "os" + "path/filepath" +) + +func unzip(archive, target string) error { + reader, err := zip.OpenReader(archive) + if err != nil { + return err + } + + if err := os.MkdirAll(target, 0750); err != nil { + return err + } + + for _, file := range reader.File { + archiveFile := file.Name + path := filepath.Join(target, archiveFile) + if file.FileInfo().IsDir() { + os.MkdirAll(path, file.Mode()) // #nosec + continue + } + + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, fileReader); err != nil { + return err + } + } + + return nil +}`, 1}} + // SampleCodeG401 - Use of weak crypto MD5 SampleCodeG401 = []CodeSample{ {` diff --git a/testutils/visitor.go b/testutils/visitor.go index df9275b..775829b 100644 --- a/testutils/visitor.go +++ b/testutils/visitor.go @@ -3,14 +3,14 @@ package testutils import ( "go/ast" - "github.com/GoASTScanner/gas" + "github.com/securego/gosec" ) // MockVisitor is useful for stubbing out ast.Visitor with callback // and looking for specific conditions to exist. type MockVisitor struct { - Context *gas.Context - Callback func(n ast.Node, ctx *gas.Context) bool + Context *gosec.Context + Callback func(n ast.Node, ctx *gosec.Context) bool } // NewMockVisitor creates a new empty struct, the Context and