Merge branch 'master' into commandcontext

This commit is contained in:
Will Roden 2018-07-26 09:13:43 -05:00
commit 6a156e2695
54 changed files with 632 additions and 451 deletions

View file

@ -2,7 +2,7 @@
### Steps to reproduce the behavior
### Gas version
### gosec version
### Go version (output of 'go version')

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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 <YOUR LOCAL WORKSPACE>:/workspace gas /workspace
docker run -it -v <YOUR LOCAL WORKSPACE>:/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:

View file

@ -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{}
}

View file

@ -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()

View file

@ -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"

View file

@ -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++
}

View file

@ -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

View file

@ -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))
}

View file

@ -8,6 +8,6 @@ package {{.}}
import (
"go/ast"
"github.com/GoASTScanner/gas"
"github.com/securego/gosec"
)
`))

View file

@ -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 }},

View file

@ -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"
)

View file

@ -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() {

View file

@ -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")
}

View file

@ -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
}

View file

@ -1,4 +1,4 @@
package gas_test
package gosec_test
import (
. "github.com/onsi/ginkgo"

View file

@ -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"

View file

@ -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

View file

@ -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())

View file

@ -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
}

View file

@ -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{

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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 <stdio.h> 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() {

60
rules/archive.go Normal file
View file

@ -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)}
}

View file

@ -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)}
}

View file

@ -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)}

View file

@ -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)",
})

View file

@ -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,

View file

@ -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)}

View file

@ -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)}
}

View file

@ -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)}

View file

@ -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")

View file

@ -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)}

View file

@ -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},

View file

@ -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)
})

View file

@ -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",
},
},

View file

@ -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)}
}

View file

@ -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")

View file

@ -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)}

View file

@ -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)}

View file

@ -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 {

View file

@ -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,

View file

@ -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)}
}

View file

@ -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",
},
}

View file

@ -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

View file

@ -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{
{`

View file

@ -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