mirror of
https://github.com/securego/gosec.git
synced 2024-11-05 19:45:51 +00:00
adding support for arbitrary paths with ...
This commit is contained in:
parent
39113216a8
commit
1a481fad70
8 changed files with 490 additions and 83 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -26,3 +26,5 @@ _testmain.go
|
|||
*.prof
|
||||
|
||||
.DS_Store
|
||||
|
||||
.vscode
|
||||
|
|
68
filelist.go
68
filelist.go
|
@ -15,60 +15,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/ryanuber/go-glob"
|
||||
)
|
||||
|
||||
type filelist struct {
|
||||
paths map[string]bool
|
||||
globs []string
|
||||
// fileList uses a map for patterns to ensure each pattern only
|
||||
// appears once
|
||||
type fileList struct {
|
||||
patterns map[string]struct{}
|
||||
}
|
||||
|
||||
func newFileList(paths ...string) *filelist {
|
||||
|
||||
f := &filelist{
|
||||
make(map[string]bool),
|
||||
make([]string, 0),
|
||||
func newFileList(paths ...string) *fileList {
|
||||
f := &fileList{
|
||||
patterns: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if e := f.Set(path); e != nil {
|
||||
// #nosec
|
||||
fmt.Fprintf(os.Stderr, "Unable to add %s to filelist: %s\n", path, e)
|
||||
}
|
||||
for _, p := range paths {
|
||||
f.patterns[p] = struct{}{}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *filelist) String() string {
|
||||
return strings.Join(f.globs, ", ")
|
||||
func (f *fileList) String() string {
|
||||
ps := make([]string, 0, len(f.patterns))
|
||||
for p := range f.patterns {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return strings.Join(ps, ", ")
|
||||
}
|
||||
|
||||
func (f *filelist) Set(path string) error {
|
||||
f.globs = append(f.globs, path)
|
||||
matches, e := filepath.Glob(path)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
for _, each := range matches {
|
||||
abs, e := filepath.Abs(each)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
f.paths[abs] = true
|
||||
func (f *fileList) Set(path string) error {
|
||||
if path == "" {
|
||||
// don't bother adding the empty path
|
||||
return nil
|
||||
}
|
||||
f.patterns[path] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f filelist) Contains(path string) bool {
|
||||
_, present := f.paths[path]
|
||||
return present
|
||||
func (f fileList) Contains(path string) bool {
|
||||
for p := range f.patterns {
|
||||
if glob.Glob(p, path) {
|
||||
log.Printf("excluding: %s\n", path)
|
||||
return true
|
||||
}
|
||||
}
|
||||
log.Printf("including: %s\n", path)
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
func (f filelist) Dump() {
|
||||
func (f fileList) Dump() {
|
||||
for k, _ := range f.paths {
|
||||
println(k)
|
||||
}
|
||||
|
|
245
filelist_test.go
Normal file
245
filelist_test.go
Normal file
|
@ -0,0 +1,245 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_newFileList(t *testing.T) {
|
||||
type args struct {
|
||||
paths []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *fileList
|
||||
}{
|
||||
{
|
||||
name: "nil paths",
|
||||
args: args{paths: nil},
|
||||
want: &fileList{patterns: map[string]struct{}{}},
|
||||
},
|
||||
{
|
||||
name: "empty paths",
|
||||
args: args{paths: []string{}},
|
||||
want: &fileList{patterns: map[string]struct{}{}},
|
||||
},
|
||||
{
|
||||
name: "have paths",
|
||||
args: args{paths: []string{"*_test.go"}},
|
||||
want: &fileList{patterns: map[string]struct{}{
|
||||
"*_test.go": struct{}{},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := newFileList(tt.args.paths...); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%q. newFileList() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fileList_String(t *testing.T) {
|
||||
type fields struct {
|
||||
patterns []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nil patterns",
|
||||
fields: fields{patterns: nil},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty patterns",
|
||||
fields: fields{patterns: []string{}},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "one pattern",
|
||||
fields: fields{patterns: []string{"foo"}},
|
||||
want: "foo",
|
||||
},
|
||||
{
|
||||
name: "two patterns",
|
||||
fields: fields{patterns: []string{"foo", "bar"}},
|
||||
want: "foo, bar",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
f := newFileList(tt.fields.patterns...)
|
||||
if got := f.String(); got != tt.want {
|
||||
t.Errorf("%q. fileList.String() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fileList_Set(t *testing.T) {
|
||||
type fields struct {
|
||||
patterns []string
|
||||
}
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want map[string]struct{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "add empty path",
|
||||
fields: fields{patterns: nil},
|
||||
args: args{path: ""},
|
||||
want: map[string]struct{}{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add path to nil patterns",
|
||||
fields: fields{patterns: nil},
|
||||
args: args{path: "foo"},
|
||||
want: map[string]struct{}{
|
||||
"foo": struct{}{},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add path to empty patterns",
|
||||
fields: fields{patterns: []string{}},
|
||||
args: args{path: "foo"},
|
||||
want: map[string]struct{}{
|
||||
"foo": struct{}{},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add path to populated patterns",
|
||||
fields: fields{patterns: []string{"foo"}},
|
||||
args: args{path: "bar"},
|
||||
want: map[string]struct{}{
|
||||
"foo": struct{}{},
|
||||
"bar": struct{}{},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
f := newFileList(tt.fields.patterns...)
|
||||
if err := f.Set(tt.args.path); (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. fileList.Set() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(f.patterns, tt.want) {
|
||||
t.Errorf("%q. got state fileList.patterns = %v, want state %v", tt.name, f.patterns, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fileList_Contains(t *testing.T) {
|
||||
type fields struct {
|
||||
patterns []string
|
||||
}
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil patterns",
|
||||
fields: fields{patterns: nil},
|
||||
args: args{path: "foo"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty patterns",
|
||||
fields: fields{patterns: nil},
|
||||
args: args{path: "foo"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "one pattern, no wildcard, no match",
|
||||
fields: fields{patterns: []string{"foo"}},
|
||||
args: args{path: "bar"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "one pattern, no wildcard, match",
|
||||
fields: fields{patterns: []string{"foo"}},
|
||||
args: args{path: "foo"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "one pattern, wildcard prefix, match",
|
||||
fields: fields{patterns: []string{"*foo"}},
|
||||
args: args{path: "foo"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "one pattern, wildcard suffix, match",
|
||||
fields: fields{patterns: []string{"foo*"}},
|
||||
args: args{path: "foo"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "one pattern, wildcard both ends, match",
|
||||
fields: fields{patterns: []string{"*foo*"}},
|
||||
args: args{path: "foo"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "default test match 1",
|
||||
fields: fields{patterns: []string{"*_test.go"}},
|
||||
args: args{path: "foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "default test match 2",
|
||||
fields: fields{patterns: []string{"*_test.go"}},
|
||||
args: args{path: "bar/foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "default test match 3",
|
||||
fields: fields{patterns: []string{"*_test.go"}},
|
||||
args: args{path: "/bar/foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "default test match 4",
|
||||
fields: fields{patterns: []string{"*_test.go"}},
|
||||
args: args{path: "baz/bar/foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "default test match 5",
|
||||
fields: fields{patterns: []string{"*_test.go"}},
|
||||
args: args{path: "/baz/bar/foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "many patterns, no match",
|
||||
fields: fields{patterns: []string{"*_one.go", "*_two.go"}},
|
||||
args: args{path: "/baz/bar/foo_test.go"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "many patterns, match",
|
||||
fields: fields{patterns: []string{"*_one.go", "*_two.go", "*_test.go"}},
|
||||
args: args{path: "/baz/bar/foo_test.go"},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
f := newFileList(tt.fields.patterns...)
|
||||
if got := f.Contains(tt.args.path); got != tt.want {
|
||||
t.Errorf("%q. fileList.Contains() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
139
main.go
139
main.go
|
@ -29,22 +29,30 @@ import (
|
|||
"github.com/GoASTScanner/gas/output"
|
||||
)
|
||||
|
||||
// #nosec flag
|
||||
var flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
|
||||
type recursion bool
|
||||
|
||||
// format output
|
||||
var flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text")
|
||||
const (
|
||||
recurse recursion = true
|
||||
noRecurse recursion = false
|
||||
)
|
||||
|
||||
// output file
|
||||
var flagOutput = flag.String("out", "", "Set output file for results")
|
||||
var (
|
||||
// #nosec flag
|
||||
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
|
||||
|
||||
// config file
|
||||
var flagConfig = flag.String("conf", "", "Path to optional config file")
|
||||
// format output
|
||||
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text")
|
||||
|
||||
// quiet
|
||||
var flagQuiet = flag.Bool("quiet", false, "Only show output when errors are found")
|
||||
// output file
|
||||
flagOutput = flag.String("out", "", "Set output file for results")
|
||||
|
||||
var usageText = `
|
||||
// config file
|
||||
flagConfig = flag.String("conf", "", "Path to optional config file")
|
||||
|
||||
// quiet
|
||||
flagQuiet = flag.Bool("quiet", false, "Only show output when errors are found")
|
||||
|
||||
usageText = `
|
||||
GAS - Go AST Scanner
|
||||
|
||||
Gas analyzes Go source code to look for common programming mistakes that
|
||||
|
@ -67,7 +75,8 @@ USAGE:
|
|||
|
||||
`
|
||||
|
||||
var logger *log.Logger
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
func extendConfList(conf map[string]interface{}, name string, inputStr string) {
|
||||
if inputStr == "" {
|
||||
|
@ -145,7 +154,7 @@ func main() {
|
|||
flag.Usage = usage
|
||||
|
||||
// Exclude files
|
||||
excluded := newFileList("**/*_test.go")
|
||||
excluded := newFileList("*_test.go")
|
||||
flag.Var(excluded, "skip", "File pattern to exclude from scan")
|
||||
|
||||
incRules := ""
|
||||
|
@ -183,42 +192,11 @@ func main() {
|
|||
analyzer := gas.NewAnalyzer(config, logger)
|
||||
AddRules(&analyzer, config)
|
||||
|
||||
// Traverse directory structure if './...'
|
||||
if flag.NArg() == 1 && flag.Arg(0) == "./..." {
|
||||
toAnalyze := getFilesToAnalyze(flag.Args(), excluded)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logger.Fatalf("Unable to traverse path %s, reason - %s", flag.Arg(0), err)
|
||||
}
|
||||
filepath.Walk(cwd, func(path string, info os.FileInfo, err error) error {
|
||||
if excluded.Contains(path) && info.IsDir() {
|
||||
logger.Printf("Skipping %s\n", path)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !info.IsDir() && !excluded.Contains(path) &&
|
||||
strings.HasSuffix(path, ".go") {
|
||||
err = analyzer.Process(path)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
// Process each file individually
|
||||
for _, filename := range flag.Args() {
|
||||
if finfo, err := os.Stat(filename); err == nil {
|
||||
if !finfo.IsDir() && !excluded.Contains(filename) &&
|
||||
strings.HasSuffix(filename, ".go") {
|
||||
if err = analyzer.Process(filename); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
for _, file := range toAnalyze {
|
||||
if err := analyzer.Process(file); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,3 +223,68 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// getFilesToAnalyze lists all files
|
||||
func getFilesToAnalyze(paths []string, excluded *fileList) []string {
|
||||
log.Println("getFilesToAnalyze: start")
|
||||
var toAnalyze []string
|
||||
for _, path := range paths {
|
||||
log.Printf("getFilesToAnalyze: processing \"%s\"\n", path)
|
||||
// get the absolute path before doing anything else
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if filepath.Base(path) == "..." {
|
||||
toAnalyze = append(
|
||||
toAnalyze,
|
||||
listFiles(filepath.Dir(path), recurse, excluded)...,
|
||||
)
|
||||
} else {
|
||||
var (
|
||||
finfo os.FileInfo
|
||||
err error
|
||||
)
|
||||
if finfo, err = os.Stat(path); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
if !finfo.IsDir() {
|
||||
if shouldInclude(path, excluded) {
|
||||
toAnalyze = append(toAnalyze, path)
|
||||
}
|
||||
} else {
|
||||
toAnalyze = listFiles(path, noRecurse, excluded)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("getFilesToAnalyze: end")
|
||||
return toAnalyze
|
||||
}
|
||||
|
||||
// listFiles returns a list of all files found that pass the shouldInclude check.
|
||||
// If doRecursiveWalk it true, it will walk the tree rooted at absPath, otherwise it
|
||||
// will only include files directly within the dir referenced by absPath.
|
||||
func listFiles(absPath string, doRecursiveWalk recursion, excluded *fileList) []string {
|
||||
var files []string
|
||||
|
||||
walk := func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() && doRecursiveWalk == noRecurse {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if shouldInclude(path, excluded) {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.Walk(absPath, walk); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
// shouldInclude checks if a specific path which is expected to reference
|
||||
// a regular file should be included
|
||||
func shouldInclude(path string, excluded *fileList) bool {
|
||||
return filepath.Ext(path) == ".go" && !excluded.Contains(path)
|
||||
}
|
||||
|
|
45
main_test.go
Normal file
45
main_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_shouldInclude(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
excluded *fileList
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "non .go file",
|
||||
args: args{
|
||||
path: "thing.txt",
|
||||
excluded: newFileList(),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: ".go file, not excluded",
|
||||
args: args{
|
||||
path: "thing.go",
|
||||
excluded: newFileList(),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: ".go file, excluded",
|
||||
args: args{
|
||||
path: "thing.go",
|
||||
excluded: newFileList("thing.go"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := shouldInclude(tt.args.path, tt.args.excluded); got != tt.want {
|
||||
t.Errorf("%q. shouldInclude() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
2
vendor.conf
Normal file
2
vendor.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Import path | revision | Repository(optional)
|
||||
github.com/ryanuber/go-glob 572520ed46dbddaed19ea3d9541bdd0494163693
|
21
vendor/github.com/ryanuber/go-glob/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ryanuber/go-glob/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Ryan Uber
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
51
vendor/github.com/ryanuber/go-glob/glob.go
generated
vendored
Normal file
51
vendor/github.com/ryanuber/go-glob/glob.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package glob
|
||||
|
||||
import "strings"
|
||||
|
||||
// The character which is treated like a glob
|
||||
const GLOB = "*"
|
||||
|
||||
// Glob will test a string pattern, potentially containing globs, against a
|
||||
// subject string. The result is a simple true/false, determining whether or
|
||||
// not the glob pattern matched the subject text.
|
||||
func Glob(pattern, subj string) bool {
|
||||
// Empty pattern can only match empty subject
|
||||
if pattern == "" {
|
||||
return subj == pattern
|
||||
}
|
||||
|
||||
// If the pattern _is_ a glob, it matches everything
|
||||
if pattern == GLOB {
|
||||
return true
|
||||
}
|
||||
|
||||
parts := strings.Split(pattern, GLOB)
|
||||
|
||||
if len(parts) == 1 {
|
||||
// No globs in pattern, so test for equality
|
||||
return subj == pattern
|
||||
}
|
||||
|
||||
leadingGlob := strings.HasPrefix(pattern, GLOB)
|
||||
trailingGlob := strings.HasSuffix(pattern, GLOB)
|
||||
end := len(parts) - 1
|
||||
|
||||
// Check the first section. Requires special handling.
|
||||
if !leadingGlob && !strings.HasPrefix(subj, parts[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Go over the middle parts and ensure they match.
|
||||
for i := 1; i < end; i++ {
|
||||
if !strings.Contains(subj, parts[i]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Trim evaluated text from subj as we loop over the pattern.
|
||||
idx := strings.Index(subj, parts[i]) + len(parts[i])
|
||||
subj = subj[idx:]
|
||||
}
|
||||
|
||||
// Reached the last section. Requires special handling.
|
||||
return trailingGlob || strings.HasSuffix(subj, parts[end])
|
||||
}
|
Loading…
Reference in a new issue