From 717706e8159c4124c2576a0defc0078493655936 Mon Sep 17 00:00:00 2001 From: oittaa <8972248+oittaa@users.noreply.github.com> Date: Fri, 2 Jan 2026 08:58:08 +0100 Subject: [PATCH] feat(rules): add support for detecting high entropy strings in composite literals (#1447) --- rules/hardcoded_credentials.go | 42 +++++++++- testutils/g101_samples.go | 136 ++++++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/rules/hardcoded_credentials.go b/rules/hardcoded_credentials.go index c10d18b..4424f1e 100644 --- a/rules/hardcoded_credentials.go +++ b/rules/hardcoded_credentials.go @@ -229,6 +229,8 @@ func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error return r.matchValueSpec(node, ctx) case *ast.BinaryExpr: return r.matchEqualityCheck(node, ctx) + case *ast.CompositeLit: + return r.matchCompositeLit(node, ctx) } return nil, nil } @@ -334,6 +336,44 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec. return nil, nil } +func (r *credentials) matchCompositeLit(lit *ast.CompositeLit, ctx *gosec.Context) (*issue.Issue, error) { + for _, elt := range lit.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + // Check if the key matches the credential pattern (struct field name or map string literal key) + matchedKey := false + if ident, ok := kv.Key.(*ast.Ident); ok { + if r.pattern.MatchString(ident.Name) { + matchedKey = true + } + } + if keyStr, err := gosec.GetString(kv.Key); err == nil { + if r.pattern.MatchString(keyStr) { + matchedKey = true + } + } + + // If key matches, check value for high entropy (generic credential warning) + if matchedKey { + if val, err := gosec.GetString(kv.Value); err == nil { + if r.ignoreEntropy || r.isHighEntropyString(val) { + return ctx.NewIssue(lit, r.ID(), r.What, r.Severity, r.Confidence), nil + } + } + } + + // Separately check value for specific secret patterns (regardless of key) + if val, err := gosec.GetString(kv.Value); err == nil { + if r.ignoreEntropy || r.isHighEntropyString(val) { + if ok, patternName := r.isSecretPattern(val); ok { + return ctx.NewIssue(lit, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil + } + } + } + } + } + return nil, nil +} + // NewHardcodedCredentials attempts to find high entropy string constants being // assigned to variables that appear to be related to credentials. func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { @@ -390,5 +430,5 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No Confidence: issue.Low, Severity: issue.High, }, - }, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil)} + }, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil), (*ast.CompositeLit)(nil)} } diff --git a/testutils/g101_samples.go b/testutils/g101_samples.go index 1dbbea8..62902e6 100644 --- a/testutils/g101_samples.go +++ b/testutils/g101_samples.go @@ -364,7 +364,123 @@ const ( func main() { fmt.Printf("%s\n", ConfigLearnerTokenAuth) } - + +`}, 0, gosec.NewConfig()}, + {[]string{` +package main + +type DBConfig struct { + Password string +} + +func main() { + _ = DBConfig{ + Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +type DBConfig struct { + Password string +} + +func main() { + _ = &DBConfig{ + Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = struct{ Password string }{ + Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = map[string]string{ + "password": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = map[string]string{ + "apiKey": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +type Config struct { + Username string + Password string +} + +func main() { + _ = Config{ + Username: "admin", + Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +type DBConfig struct { + Password string +} + +func main() { + _ = DBConfig{ // #nosec G101 + Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", + } +} +`}, 0, gosec.NewConfig()}, + // Negatives + {[]string{` +package main + +func main() { + _ = struct{ Password string }{ + Password: "secret", // low entropy + } +} +`}, 0, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = map[string]string{ + "password": "secret", // low entropy + } +} +`}, 0, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = struct{ Username string }{ + Username: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", // non-sensitive key + } +} +`}, 0, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = []string{"f62e5bcda4fae4f82370da0c6f20697b8f8447ef"} // unkeyed – no trigger +} `}, 0, gosec.NewConfig()}, } @@ -430,6 +546,24 @@ func main() { if compareGoogleAPI == "AIzajtGS_aJGkoiAmSbXzu9I-1eytAi9Lrlh-vT" { fmt.Println(compareGoogleAPI) } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = struct{ SomeKey string }{ + SomeKey: "AKIAI44QH8DHBEXAMPLE", + } +} +`}, 1, gosec.NewConfig()}, + {[]string{` +package main + +func main() { + _ = map[string]string{ + "github_token": "ghp_iR54dhCYg9Tfmoywi9xLmmKZrrnAw438BYh3", + } } `}, 1, gosec.NewConfig()}, }