Skip to content

Commit 7387d22

Browse files
authored
Refactor rules to use callListRule base structure (#1458)
* Refactor rules to utilize callListRule base structure - Introduced a new base structure `callListRule` in `rules/base.go` to standardize the implementation of rules that check for specific function calls. - Updated existing rules to inherit from `callListRule`, simplifying their structure and removing redundant ID methods. - Modified the `MetaData` field to use `RuleID` instead of `ID` for consistency across rules. - Removed the `weakcryptohash.go` and `weakdepricatedcryptohash.go` files as their functionality has been integrated into the new structure. * fix(tlsconfig): correct MetaData field name in generated TLS check * refactor: standardize rule metadata and call list initialization
1 parent 52f5dbf commit 7387d22

33 files changed

Lines changed: 212 additions & 610 deletions

cmd/tlsconfig/rule_template.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var generatedRuleTmpl = template.Must(template.New("generated").Parse(`
77
// DO NOT EDIT - generated by tlsconfig tool
88
func New{{.Name}}TLSCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
99
return &insecureConfigTLS{
10-
MetaData: issue.MetaData{ID: id},
10+
MetaData: issue.MetaData{RuleID: id},
1111
requiredType: "crypto/tls.Config",
1212
MinVersion: {{ .MinVersion }},
1313
MaxVersion: {{ .MaxVersion }},

issue/issue.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,28 @@ func (i *Issue) FileLocation() string {
128128
// MetaData is embedded in all gosec rules. The Severity, Confidence and What message
129129
// will be passed through to reported issues.
130130
type MetaData struct {
131-
ID string
131+
RuleID string
132132
Severity Score
133133
Confidence Score
134134
What string
135135
}
136136

137+
// NewMetaData creates a new MetaData object
138+
func NewMetaData(id, what string, severity, confidence Score) MetaData {
139+
return MetaData{
140+
RuleID: id,
141+
What: what,
142+
Severity: severity,
143+
Confidence: confidence,
144+
}
145+
}
146+
147+
// ID returns the rule ID. This satisfies part of the gosec.Rule interface
148+
// when MetaData is embedded in a rule struct.
149+
func (m MetaData) ID() string {
150+
return m.RuleID
151+
}
152+
137153
// MarshalJSON is used convert a Score object into a JSON representation
138154
func (c Score) MarshalJSON() ([]byte, error) {
139155
return json.Marshal(c.String())

rules/archive.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,10 @@ import (
1111
)
1212

1313
type archive struct {
14-
issue.MetaData
15-
calls gosec.CallList
14+
callListRule
1615
argTypes []string
1716
}
1817

19-
func (a *archive) ID() string {
20-
return a.MetaData.ID
21-
}
22-
2318
// getArchiveBaseType returns the underlying type (*archive/zip.File or *archive/tar.Header)
2419
// if the expression is a direct .Name selector on such a type or a short-declared variable
2520
// assigned from such a selector (e.g., name := file.Name).
@@ -72,17 +67,10 @@ func (a *archive) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
7267

7368
// NewArchive creates a new rule which detects file traversal when extracting zip/tar archives.
7469
func NewArchive(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
75-
calls := gosec.NewCallList()
76-
calls.Add("path/filepath", "Join")
77-
calls.Add("path", "Join")
78-
return &archive{
79-
calls: calls,
80-
argTypes: []string{"*archive/zip.File", "*archive/tar.Header"},
81-
MetaData: issue.MetaData{
82-
ID: id,
83-
Severity: issue.Medium,
84-
Confidence: issue.High,
85-
What: "File traversal when extracting zip/tar archive",
86-
},
87-
}, []ast.Node{(*ast.CallExpr)(nil)}
70+
rule := &archive{
71+
callListRule: newCallListRule(id, "File traversal when extracting zip/tar archive", issue.Medium, issue.High),
72+
argTypes: []string{"*archive/zip.File", "*archive/tar.Header"},
73+
}
74+
rule.Add("path/filepath", "Join").Add("path", "Join")
75+
return rule, []ast.Node{(*ast.CallExpr)(nil)}
8876
}

rules/base.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package rules
2+
3+
import (
4+
"go/ast"
5+
6+
"github.com/securego/gosec/v2"
7+
"github.com/securego/gosec/v2/issue"
8+
)
9+
10+
// callListRule is a base for rules that simply check a CallList and issue on match.
11+
// It provides the standard Match() implementation used by most call-based rules.
12+
type callListRule struct {
13+
issue.MetaData
14+
calls gosec.CallList
15+
}
16+
17+
func newCallListRule(id, what string, severity, confidence issue.Score) callListRule {
18+
return callListRule{
19+
MetaData: issue.NewMetaData(id, what, severity, confidence),
20+
calls: gosec.NewCallList(),
21+
}
22+
}
23+
24+
func (r *callListRule) Add(selector, ident string) *callListRule {
25+
r.calls.Add(selector, ident)
26+
return r
27+
}
28+
29+
func (r *callListRule) AddAll(selector string, idents ...string) *callListRule {
30+
r.calls.AddAll(selector, idents...)
31+
return r
32+
}
33+
34+
func (r *callListRule) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
35+
if r.calls.ContainsPkgCallExpr(n, c, false) != nil {
36+
return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil
37+
}
38+
return nil, nil
39+
}

rules/bind.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,10 @@ import (
2424

2525
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
2626
type bindsToAllNetworkInterfaces struct {
27-
issue.MetaData
28-
calls gosec.CallList
27+
callListRule
2928
pattern *regexp.Regexp
3029
}
3130

32-
func (r *bindsToAllNetworkInterfaces) ID() string {
33-
return r.MetaData.ID
34-
}
35-
3631
func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
3732
callExpr := r.calls.ContainsPkgCallExpr(n, c, false)
3833
if callExpr == nil {
@@ -68,17 +63,10 @@ func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*issu
6863
// NewBindsToAllNetworkInterfaces detects socket connections that are setup to
6964
// listen on all network interfaces.
7065
func NewBindsToAllNetworkInterfaces(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
71-
calls := gosec.NewCallList()
72-
calls.Add("net", "Listen")
73-
calls.Add("crypto/tls", "Listen")
74-
return &bindsToAllNetworkInterfaces{
75-
calls: calls,
76-
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
77-
MetaData: issue.MetaData{
78-
ID: id,
79-
Severity: issue.Medium,
80-
Confidence: issue.High,
81-
What: "Binds to all network interfaces",
82-
},
83-
}, []ast.Node{(*ast.CallExpr)(nil)}
66+
rule := &bindsToAllNetworkInterfaces{
67+
callListRule: newCallListRule(id, "Binds to all network interfaces", issue.Medium, issue.High),
68+
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
69+
}
70+
rule.Add("net", "Listen").Add("crypto/tls", "Listen")
71+
return rule, []ast.Node{(*ast.CallExpr)(nil)}
8472
}

rules/blocklist.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ func unquote(original string) string {
3333
return strings.TrimRight(cleaned, `"`)
3434
}
3535

36-
func (r *blocklistedImport) ID() string {
37-
return r.MetaData.ID
38-
}
39-
4036
func (r *blocklistedImport) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
4137
if node, ok := n.(*ast.ImportSpec); ok {
4238
if description, ok := r.Blocklisted[unquote(node.Path.Value)]; ok {
@@ -50,11 +46,7 @@ func (r *blocklistedImport) Match(n ast.Node, c *gosec.Context) (*issue.Issue, e
5046
// Typically when a deprecated technology is being used.
5147
func NewBlocklistedImports(id string, _ gosec.Config, blocklist map[string]string) (gosec.Rule, []ast.Node) {
5248
return &blocklistedImport{
53-
MetaData: issue.MetaData{
54-
ID: id,
55-
Severity: issue.Medium,
56-
Confidence: issue.High,
57-
},
49+
MetaData: issue.NewMetaData(id, "", issue.Medium, issue.High),
5850
Blocklisted: blocklist,
5951
}, []ast.Node{(*ast.ImportSpec)(nil)}
6052
}

rules/decompression_bomb.go

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ type decompressionBombCheck struct {
2929
copyCalls gosec.CallList
3030
}
3131

32-
func (d *decompressionBombCheck) ID() string {
33-
return d.MetaData.ID
34-
}
35-
3632
func containsReaderCall(node ast.Node, ctx *gosec.Context, list gosec.CallList) bool {
3733
if list.ContainsPkgCallExpr(node, ctx, false) != nil {
3834
return true
@@ -91,28 +87,21 @@ func (d *decompressionBombCheck) Match(node ast.Node, ctx *gosec.Context) (*issu
9187

9288
// NewDecompressionBombCheck detects potential DoS via decompression bomb
9389
func NewDecompressionBombCheck(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
94-
readerCalls := gosec.NewCallList()
95-
readerCalls.Add("compress/gzip", "NewReader")
96-
readerCalls.AddAll("compress/zlib", "NewReader", "NewReaderDict")
97-
readerCalls.Add("compress/bzip2", "NewReader")
98-
readerCalls.AddAll("compress/flate", "NewReader", "NewReaderDict")
99-
readerCalls.Add("compress/lzw", "NewReader")
100-
readerCalls.Add("archive/tar", "NewReader")
101-
readerCalls.Add("archive/zip", "NewReader")
102-
readerCalls.Add("*archive/zip.File", "Open")
90+
rule := &decompressionBombCheck{
91+
MetaData: issue.NewMetaData(id, "Potential DoS vulnerability via decompression bomb", issue.Medium, issue.Medium),
92+
readerCalls: gosec.NewCallList(),
93+
copyCalls: gosec.NewCallList(),
94+
}
95+
rule.readerCalls.Add("compress/gzip", "NewReader")
96+
rule.readerCalls.AddAll("compress/zlib", "NewReader", "NewReaderDict")
97+
rule.readerCalls.Add("compress/bzip2", "NewReader")
98+
rule.readerCalls.AddAll("compress/flate", "NewReader", "NewReaderDict")
99+
rule.readerCalls.Add("compress/lzw", "NewReader")
100+
rule.readerCalls.Add("archive/tar", "NewReader")
101+
rule.readerCalls.Add("archive/zip", "NewReader")
102+
rule.readerCalls.Add("*archive/zip.File", "Open")
103103

104-
copyCalls := gosec.NewCallList()
105-
copyCalls.Add("io", "Copy")
106-
copyCalls.Add("io", "CopyBuffer")
104+
rule.copyCalls.AddAll("io", "Copy", "CopyBuffer")
107105

108-
return &decompressionBombCheck{
109-
MetaData: issue.MetaData{
110-
ID: id,
111-
Severity: issue.Medium,
112-
Confidence: issue.Medium,
113-
What: "Potential DoS vulnerability via decompression bomb",
114-
},
115-
readerCalls: readerCalls,
116-
copyCalls: copyCalls,
117-
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.CallExpr)(nil)}
106+
return rule, []ast.Node{(*ast.AssignStmt)(nil), (*ast.CallExpr)(nil)}
118107
}

rules/directory_traversal.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ type traversal struct {
1313
issue.MetaData
1414
}
1515

16-
func (r *traversal) ID() string {
17-
return r.MetaData.ID
18-
}
19-
2016
func (r *traversal) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
2117
switch node := n.(type) {
2218
case *ast.CallExpr:
@@ -54,12 +50,7 @@ func NewDirectoryTraversal(id string, conf gosec.Config) (gosec.Rule, []ast.Node
5450
}
5551

5652
return &traversal{
57-
pattern: regexp.MustCompile(pattern),
58-
MetaData: issue.MetaData{
59-
ID: id,
60-
What: "Potential directory traversal",
61-
Confidence: issue.Medium,
62-
Severity: issue.Medium,
63-
},
53+
pattern: regexp.MustCompile(pattern),
54+
MetaData: issue.NewMetaData(id, "Potential directory traversal", issue.Medium, issue.Medium),
6455
}, []ast.Node{(*ast.CallExpr)(nil)}
6556
}

rules/errors.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ type noErrorCheck struct {
2727
whitelist gosec.CallList
2828
}
2929

30-
func (r *noErrorCheck) ID() string {
31-
return r.MetaData.ID
32-
}
33-
3430
func returnsError(callExpr *ast.CallExpr, ctx *gosec.Context) int {
3531
if tv := ctx.Info.TypeOf(callExpr); tv != nil {
3632
switch t := tv.(type) {
@@ -102,12 +98,7 @@ func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
10298
}
10399

104100
return &noErrorCheck{
105-
MetaData: issue.MetaData{
106-
ID: id,
107-
Severity: issue.Low,
108-
Confidence: issue.High,
109-
What: "Errors unhandled",
110-
},
101+
MetaData: issue.NewMetaData(id, "Errors unhandled", issue.Low, issue.High),
111102
whitelist: whitelist,
112103
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ExprStmt)(nil)}
113104
}

rules/fileperms.go

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ type filePermissions struct {
3030
calls []string
3131
}
3232

33-
// ID returns the ID of the rule.
34-
func (r *filePermissions) ID() string {
35-
return r.MetaData.ID
36-
}
37-
3833
func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMode int64) int64 {
3934
mode := defaultMode
4035
if value, ok := conf[configKey]; ok {
@@ -85,15 +80,10 @@ func isOsPerm(n ast.Node) bool {
8580
func NewWritePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
8681
mode := getConfiguredMode(conf, id, 0o600)
8782
return &filePermissions{
88-
mode: mode,
89-
pkgs: []string{"io/ioutil", "os"},
90-
calls: []string{"WriteFile"},
91-
MetaData: issue.MetaData{
92-
ID: id,
93-
Severity: issue.Medium,
94-
Confidence: issue.High,
95-
What: fmt.Sprintf("Expect WriteFile permissions to be %#o or less", mode),
96-
},
83+
mode: mode,
84+
pkgs: []string{"io/ioutil", "os"},
85+
calls: []string{"WriteFile"},
86+
MetaData: issue.NewMetaData(id, fmt.Sprintf("Expect WriteFile permissions to be %#o or less", mode), issue.Medium, issue.High),
9787
}, []ast.Node{(*ast.CallExpr)(nil)}
9888
}
9989

@@ -102,15 +92,10 @@ func NewWritePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
10292
func NewFilePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
10393
mode := getConfiguredMode(conf, id, 0o600)
10494
return &filePermissions{
105-
mode: mode,
106-
pkgs: []string{"os"},
107-
calls: []string{"OpenFile", "Chmod"},
108-
MetaData: issue.MetaData{
109-
ID: id,
110-
Severity: issue.Medium,
111-
Confidence: issue.High,
112-
What: fmt.Sprintf("Expect file permissions to be %#o or less", mode),
113-
},
95+
mode: mode,
96+
pkgs: []string{"os"},
97+
calls: []string{"OpenFile", "Chmod"},
98+
MetaData: issue.NewMetaData(id, fmt.Sprintf("Expect file permissions to be %#o or less", mode), issue.Medium, issue.High),
11499
}, []ast.Node{(*ast.CallExpr)(nil)}
115100
}
116101

@@ -119,15 +104,10 @@ func NewFilePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
119104
func NewMkdirPerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
120105
mode := getConfiguredMode(conf, id, 0o750)
121106
return &filePermissions{
122-
mode: mode,
123-
pkgs: []string{"os"},
124-
calls: []string{"Mkdir", "MkdirAll"},
125-
MetaData: issue.MetaData{
126-
ID: id,
127-
Severity: issue.Medium,
128-
Confidence: issue.High,
129-
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),
130-
},
107+
mode: mode,
108+
pkgs: []string{"os"},
109+
calls: []string{"Mkdir", "MkdirAll"},
110+
MetaData: issue.NewMetaData(id, fmt.Sprintf("Expect directory permissions to be %#o or less", mode), issue.Medium, issue.High),
131111
}, []ast.Node{(*ast.CallExpr)(nil)}
132112
}
133113

@@ -140,11 +120,6 @@ type osCreatePermissions struct {
140120

141121
const defaultOsCreateMode = 0o666
142122

143-
// ID returns the ID of the rule.
144-
func (r *osCreatePermissions) ID() string {
145-
return r.MetaData.ID
146-
}
147-
148123
// Match checks if the rule is matched.
149124
func (r *osCreatePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
150125
for _, pkg := range r.pkgs {
@@ -165,12 +140,7 @@ func NewOsCreatePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
165140
mode: mode,
166141
pkgs: []string{"os"},
167142
calls: []string{"Create"},
168-
MetaData: issue.MetaData{
169-
ID: id,
170-
Severity: issue.Medium,
171-
Confidence: issue.High,
172-
What: fmt.Sprintf("Expect file permissions to be %#o or less but os.Create used with default permissions %#o",
173-
mode, defaultOsCreateMode),
174-
},
143+
MetaData: issue.NewMetaData(id, fmt.Sprintf("Expect file permissions to be %#o or less but os.Create used with default permissions %#o",
144+
mode, defaultOsCreateMode), issue.Medium, issue.High),
175145
}, []ast.Node{(*ast.CallExpr)(nil)}
176146
}

0 commit comments

Comments
 (0)