15
15
package rules
16
16
17
17
import (
18
+ "fmt"
18
19
gas "github.com/GoASTScanner/gas/core"
19
20
"go/ast"
20
21
"go/token"
21
22
"regexp"
23
+
24
+ "github.com/nbutton23/zxcvbn-go"
25
+ "strconv"
22
26
)
23
27
24
28
type Credentials struct {
25
29
gas.MetaData
26
- pattern * regexp.Regexp
30
+ pattern * regexp.Regexp
31
+ entropyThreshold float64
32
+ perCharThreshold float64
33
+ truncate int64
34
+ ignoreEntropy bool
35
+ }
36
+
37
+ func (r * Credentials ) isHighEntropyString (str string ) bool {
38
+ s := fmt .Sprintf ("%.*s" , r .truncate , str )
39
+ info := zxcvbn .PasswordStrength (s , []string {})
40
+ entropyPerChar := info .Entropy / float64 (len (s ))
41
+ return (info .Entropy >= r .entropyThreshold ||
42
+ (info .Entropy >= (r .entropyThreshold / 2 ) &&
43
+ entropyPerChar >= r .perCharThreshold ))
27
44
}
28
45
29
46
func (r * Credentials ) Match (n ast.Node , ctx * gas.Context ) (* gas.Issue , error ) {
@@ -41,8 +58,10 @@ func (r *Credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*ga
41
58
if ident , ok := i .(* ast.Ident ); ok {
42
59
if r .pattern .MatchString (ident .Name ) {
43
60
for _ , e := range assign .Rhs {
44
- if rhs , ok := e .(* ast.BasicLit ); ok && rhs .Kind == token .STRING {
45
- return gas .NewIssue (ctx , assign , r .What , r .Severity , r .Confidence ), nil
61
+ if val , err := gas .GetString (e ); err == nil {
62
+ if r .ignoreEntropy || (! r .ignoreEntropy && r .isHighEntropyString (val )) {
63
+ return gas .NewIssue (ctx , assign , r .What , r .Severity , r .Confidence ), nil
64
+ }
46
65
}
47
66
}
48
67
}
@@ -75,11 +94,43 @@ func (r *Credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Is
75
94
76
95
func NewHardcodedCredentials (conf map [string ]interface {}) (gas.Rule , []ast.Node ) {
77
96
pattern := `(?i)passwd|pass|password|pwd|secret|token`
97
+ entropyThreshold := 80.0
98
+ perCharThreshold := 3.0
99
+ ignoreEntropy := false
100
+ var truncateString int64 = 16
78
101
if val , ok := conf ["G101" ]; ok {
79
- pattern = val .(string )
102
+ conf := val .(map [string ]string )
103
+ if configPattern , ok := conf ["pattern" ]; ok {
104
+ pattern = configPattern
105
+ }
106
+ if configIgnoreEntropy , ok := conf ["ignore_entropy" ]; ok {
107
+ if parsedBool , err := strconv .ParseBool (configIgnoreEntropy ); err == nil {
108
+ ignoreEntropy = parsedBool
109
+ }
110
+ }
111
+ if configEntropyThreshold , ok := conf ["entropy_threshold" ]; ok {
112
+ if parsedNum , err := strconv .ParseFloat (configEntropyThreshold , 64 ); err == nil {
113
+ entropyThreshold = parsedNum
114
+ }
115
+ }
116
+ if configCharThreshold , ok := conf ["per_char_threshold" ]; ok {
117
+ if parsedNum , err := strconv .ParseFloat (configCharThreshold , 64 ); err == nil {
118
+ perCharThreshold = parsedNum
119
+ }
120
+ }
121
+ if configTruncate , ok := conf ["truncate" ]; ok {
122
+ if parsedInt , err := strconv .ParseInt (configTruncate , 10 , 64 ); err == nil {
123
+ truncateString = parsedInt
124
+ }
125
+ }
80
126
}
127
+
81
128
return & Credentials {
82
- pattern : regexp .MustCompile (pattern ),
129
+ pattern : regexp .MustCompile (pattern ),
130
+ entropyThreshold : entropyThreshold ,
131
+ perCharThreshold : perCharThreshold ,
132
+ ignoreEntropy : ignoreEntropy ,
133
+ truncate : truncateString ,
83
134
MetaData : gas.MetaData {
84
135
What : "Potential hardcoded credentials" ,
85
136
Confidence : gas .Low ,
0 commit comments