Skip to content

Commit 717706e

Browse files
authored
feat(rules): add support for detecting high entropy strings in composite literals (#1447)
1 parent 082deb6 commit 717706e

File tree

2 files changed

+176
-2
lines changed

2 files changed

+176
-2
lines changed

rules/hardcoded_credentials.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error
229229
return r.matchValueSpec(node, ctx)
230230
case *ast.BinaryExpr:
231231
return r.matchEqualityCheck(node, ctx)
232+
case *ast.CompositeLit:
233+
return r.matchCompositeLit(node, ctx)
232234
}
233235
return nil, nil
234236
}
@@ -334,6 +336,44 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
334336
return nil, nil
335337
}
336338

339+
func (r *credentials) matchCompositeLit(lit *ast.CompositeLit, ctx *gosec.Context) (*issue.Issue, error) {
340+
for _, elt := range lit.Elts {
341+
if kv, ok := elt.(*ast.KeyValueExpr); ok {
342+
// Check if the key matches the credential pattern (struct field name or map string literal key)
343+
matchedKey := false
344+
if ident, ok := kv.Key.(*ast.Ident); ok {
345+
if r.pattern.MatchString(ident.Name) {
346+
matchedKey = true
347+
}
348+
}
349+
if keyStr, err := gosec.GetString(kv.Key); err == nil {
350+
if r.pattern.MatchString(keyStr) {
351+
matchedKey = true
352+
}
353+
}
354+
355+
// If key matches, check value for high entropy (generic credential warning)
356+
if matchedKey {
357+
if val, err := gosec.GetString(kv.Value); err == nil {
358+
if r.ignoreEntropy || r.isHighEntropyString(val) {
359+
return ctx.NewIssue(lit, r.ID(), r.What, r.Severity, r.Confidence), nil
360+
}
361+
}
362+
}
363+
364+
// Separately check value for specific secret patterns (regardless of key)
365+
if val, err := gosec.GetString(kv.Value); err == nil {
366+
if r.ignoreEntropy || r.isHighEntropyString(val) {
367+
if ok, patternName := r.isSecretPattern(val); ok {
368+
return ctx.NewIssue(lit, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
369+
}
370+
}
371+
}
372+
}
373+
}
374+
return nil, nil
375+
}
376+
337377
// NewHardcodedCredentials attempts to find high entropy string constants being
338378
// assigned to variables that appear to be related to credentials.
339379
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
390430
Confidence: issue.Low,
391431
Severity: issue.High,
392432
},
393-
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil)}
433+
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil), (*ast.BinaryExpr)(nil), (*ast.CompositeLit)(nil)}
394434
}

testutils/g101_samples.go

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,123 @@ const (
364364
func main() {
365365
fmt.Printf("%s\n", ConfigLearnerTokenAuth)
366366
}
367-
367+
368+
`}, 0, gosec.NewConfig()},
369+
{[]string{`
370+
package main
371+
372+
type DBConfig struct {
373+
Password string
374+
}
375+
376+
func main() {
377+
_ = DBConfig{
378+
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
379+
}
380+
}
381+
`}, 1, gosec.NewConfig()},
382+
{[]string{`
383+
package main
384+
385+
type DBConfig struct {
386+
Password string
387+
}
388+
389+
func main() {
390+
_ = &DBConfig{
391+
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
392+
}
393+
}
394+
`}, 1, gosec.NewConfig()},
395+
{[]string{`
396+
package main
397+
398+
func main() {
399+
_ = struct{ Password string }{
400+
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
401+
}
402+
}
403+
`}, 1, gosec.NewConfig()},
404+
{[]string{`
405+
package main
406+
407+
func main() {
408+
_ = map[string]string{
409+
"password": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
410+
}
411+
}
412+
`}, 1, gosec.NewConfig()},
413+
{[]string{`
414+
package main
415+
416+
func main() {
417+
_ = map[string]string{
418+
"apiKey": "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
419+
}
420+
}
421+
`}, 1, gosec.NewConfig()},
422+
{[]string{`
423+
package main
424+
425+
type Config struct {
426+
Username string
427+
Password string
428+
}
429+
430+
func main() {
431+
_ = Config{
432+
Username: "admin",
433+
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
434+
}
435+
}
436+
`}, 1, gosec.NewConfig()},
437+
{[]string{`
438+
package main
439+
440+
type DBConfig struct {
441+
Password string
442+
}
443+
444+
func main() {
445+
_ = DBConfig{ // #nosec G101
446+
Password: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef",
447+
}
448+
}
449+
`}, 0, gosec.NewConfig()},
450+
// Negatives
451+
{[]string{`
452+
package main
453+
454+
func main() {
455+
_ = struct{ Password string }{
456+
Password: "secret", // low entropy
457+
}
458+
}
459+
`}, 0, gosec.NewConfig()},
460+
{[]string{`
461+
package main
462+
463+
func main() {
464+
_ = map[string]string{
465+
"password": "secret", // low entropy
466+
}
467+
}
468+
`}, 0, gosec.NewConfig()},
469+
{[]string{`
470+
package main
471+
472+
func main() {
473+
_ = struct{ Username string }{
474+
Username: "f62e5bcda4fae4f82370da0c6f20697b8f8447ef", // non-sensitive key
475+
}
476+
}
477+
`}, 0, gosec.NewConfig()},
478+
{[]string{`
479+
package main
480+
481+
func main() {
482+
_ = []string{"f62e5bcda4fae4f82370da0c6f20697b8f8447ef"} // unkeyed – no trigger
483+
}
368484
`}, 0, gosec.NewConfig()},
369485
}
370486

@@ -430,6 +546,24 @@ func main() {
430546
if compareGoogleAPI == "AIzajtGS_aJGkoiAmSbXzu9I-1eytAi9Lrlh-vT" {
431547
fmt.Println(compareGoogleAPI)
432548
}
549+
}
550+
`}, 1, gosec.NewConfig()},
551+
{[]string{`
552+
package main
553+
554+
func main() {
555+
_ = struct{ SomeKey string }{
556+
SomeKey: "AKIAI44QH8DHBEXAMPLE",
557+
}
558+
}
559+
`}, 1, gosec.NewConfig()},
560+
{[]string{`
561+
package main
562+
563+
func main() {
564+
_ = map[string]string{
565+
"github_token": "ghp_iR54dhCYg9Tfmoywi9xLmmKZrrnAw438BYh3",
566+
}
433567
}
434568
`}, 1, gosec.NewConfig()},
435569
}

0 commit comments

Comments
 (0)