Skip to content

Commit 0f294b8

Browse files
authored
Merge pull request #6349 from Checkmarx/feat/mask-preview-on-disabled-secrets
feat(secrets): add secrets mask to preview lines
2 parents 16d5530 + dac16a7 commit 0f294b8

4 files changed

Lines changed: 317 additions & 4 deletions

File tree

pkg/engine/secrets/inspector.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func NewInspector(
156156
return nil, err
157157
}
158158

159-
allowRules, err := compileRegex(allRegexQueries.AllowRules)
159+
allowRules, err := CompileRegex(allRegexQueries.AllowRules)
160160
if err != nil {
161161
return nil, err
162162
}
@@ -282,7 +282,8 @@ func compileRegexQueries(
282282
return regexQueries, nil
283283
}
284284

285-
func compileRegex(allowRules []AllowRule) ([]AllowRule, error) {
285+
// CompileRegex compiles the regex allow rules
286+
func CompileRegex(allowRules []AllowRule) ([]AllowRule, error) {
286287
for j := range allowRules {
287288
compiledRegex, err := regexp.Compile(allowRules[j].RegexStr)
288289
if err != nil {
@@ -307,7 +308,7 @@ func isValueInArray(value string, array []string) bool {
307308
}
308309

309310
func (c *Inspector) isSecret(s string, query *RegexQuery) (isSecretRet bool, groups [][]string) {
310-
if isAllowRule(s, query.AllowRules) || isAllowRule(s, c.allowRules) {
311+
if IsAllowRule(s, query.AllowRules) || IsAllowRule(s, c.allowRules) {
311312
return false, [][]string{}
312313
}
313314

@@ -339,7 +340,8 @@ func (c *Inspector) isSecret(s string, query *RegexQuery) (isSecretRet bool, gro
339340
return false, [][]string{}
340341
}
341342

342-
func isAllowRule(s string, allowRules []AllowRule) bool {
343+
// IsAllowRule check if string matches any of the allow rules for the secret queries
344+
func IsAllowRule(s string, allowRules []AllowRule) bool {
343345
for i := range allowRules {
344346
if allowRules[i].Regex.MatchString(s) {
345347
return true

pkg/scan/post_scan.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ func (c *Client) postScan(scanResults *Results) error {
105105
}
106106
}
107107

108+
// mask results preview if Secrets Scan is disabled
109+
if c.ScanParams.DisableSecrets {
110+
err := maskPreviewLines(c.ScanParams.SecretsRegexesPath, scanResults)
111+
if err != nil {
112+
log.Err(err)
113+
return err
114+
}
115+
}
116+
108117
summary := c.getSummary(scanResults.Results, time.Now(), model.PathParameters{
109118
ScannedPaths: c.ScanParams.Path,
110119
PathExtractionMap: scanResults.ExtractedPaths.ExtractionMap,

pkg/scan/preview_secrets_mask.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Package scan implements functions and helpers to ensure the proper scan of the specified files
2+
package scan
3+
4+
import (
5+
"encoding/json"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/Checkmarx/kics/pkg/engine/secrets"
10+
"github.com/Checkmarx/kics/pkg/model"
11+
)
12+
13+
func maskPreviewLines(secretsPath string, scanResults *Results) error {
14+
secretsRegexRulesContent, err := getSecretsRegexRules(secretsPath)
15+
if err != nil {
16+
return err
17+
}
18+
19+
var allRegexQueries secrets.RegexRuleStruct
20+
21+
err = json.Unmarshal([]byte(secretsRegexRulesContent), &allRegexQueries)
22+
if err != nil {
23+
return err
24+
}
25+
26+
allowRules, err := secrets.CompileRegex(allRegexQueries.AllowRules)
27+
if err != nil {
28+
return err
29+
}
30+
31+
rules, err := compileRegexQueries(allRegexQueries.Rules)
32+
if err != nil {
33+
return err
34+
}
35+
36+
for i := range scanResults.Results {
37+
item := scanResults.Results[i]
38+
hideSecret(item.VulnLines, &allowRules, &rules)
39+
}
40+
return nil
41+
}
42+
43+
func compileRegexQueries(allRegexQueries []secrets.RegexQuery) ([]secrets.RegexQuery, error) {
44+
for i := range allRegexQueries {
45+
compiledRegexp, err := regexp.Compile(allRegexQueries[i].RegexStr)
46+
if err != nil {
47+
return allRegexQueries, err
48+
}
49+
allRegexQueries[i].Regex = compiledRegexp
50+
51+
for j := range allRegexQueries[i].AllowRules {
52+
allRegexQueries[i].AllowRules[j].Regex = regexp.MustCompile(allRegexQueries[i].AllowRules[j].RegexStr)
53+
}
54+
}
55+
return allRegexQueries, nil
56+
}
57+
58+
func hideSecret(lines *[]model.CodeLine, allowRules *[]secrets.AllowRule, rules *[]secrets.RegexQuery) {
59+
for idx, line := range *lines {
60+
for i := range *rules {
61+
rule := (*rules)[i]
62+
63+
isSecret, groups := isSecret(line.Line, &rule, allowRules)
64+
// if not a secret skip to next line
65+
if !isSecret {
66+
continue
67+
}
68+
69+
if len(rule.Entropies) == 0 {
70+
maskSecret(&rule, lines, idx)
71+
}
72+
73+
if len(groups[0]) > 0 {
74+
for _, entropy := range rule.Entropies {
75+
// if matched group does not exist continue
76+
if len(groups[0]) <= entropy.Group {
77+
return
78+
}
79+
isMatch, _ := secrets.CheckEntropyInterval(
80+
entropy,
81+
groups[0][entropy.Group],
82+
)
83+
if isMatch {
84+
maskSecret(&rule, lines, idx)
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
func maskSecret(rule *secrets.RegexQuery, lines *[]model.CodeLine, idx int) {
93+
if rule.SpecialMask == "all" {
94+
(*lines)[idx].Line = "<SECRET-MASKED-ON-PURPOSE>"
95+
return
96+
}
97+
98+
regex := rule.RegexStr
99+
line := (*lines)[idx]
100+
101+
if len(rule.SpecialMask) > 0 {
102+
regex = "(.+)" + rule.SpecialMask
103+
}
104+
105+
var re = regexp.MustCompile(regex)
106+
match := re.FindString(line.Line)
107+
108+
if len(rule.SpecialMask) > 0 {
109+
match = line.Line[len(match):]
110+
}
111+
112+
if match != "" {
113+
(*lines)[idx].Line = strings.Replace(line.Line, match, "<SECRET-MASKED-ON-PURPOSE>", 1)
114+
} else {
115+
(*lines)[idx].Line = "<SECRET-MASKED-ON-PURPOSE>"
116+
}
117+
}
118+
119+
// repurposed isSecret from inspector
120+
func isSecret(line string, rule *secrets.RegexQuery, allowRules *[]secrets.AllowRule) (isSecretRet bool, groups [][]string) {
121+
if secrets.IsAllowRule(line, *allowRules) {
122+
return false, [][]string{}
123+
}
124+
125+
groups = rule.Regex.FindAllStringSubmatch(line, -1)
126+
127+
for _, group := range groups {
128+
splitedText := strings.Split(line, "\n")
129+
max := -1
130+
for i, splited := range splitedText {
131+
if len(groups) < rule.Multiline.DetectLineGroup {
132+
if strings.Contains(splited, group[rule.Multiline.DetectLineGroup]) && i > max {
133+
max = i
134+
}
135+
}
136+
}
137+
if max == -1 {
138+
continue
139+
}
140+
secret, newGroups := isSecret(strings.Join(append(splitedText[:max], splitedText[max+1:]...), "\n"), rule, allowRules)
141+
if !secret {
142+
continue
143+
}
144+
groups = append(groups, newGroups...)
145+
}
146+
147+
if len(groups) > 0 {
148+
return true, groups
149+
}
150+
return false, [][]string{}
151+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package scan
2+
3+
import (
4+
"github.com/Checkmarx/kics/internal/tracker"
5+
"github.com/Checkmarx/kics/pkg/model"
6+
"github.com/Checkmarx/kics/pkg/printer"
7+
"github.com/Checkmarx/kics/pkg/progress"
8+
"github.com/stretchr/testify/require"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func Test_maskSecrets(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
filename string
17+
scanParameters Parameters
18+
tracker tracker.CITracker
19+
scanResults *Results
20+
}{
21+
{
22+
name: "print with masked secrets",
23+
filename: "results",
24+
scanParameters: Parameters{
25+
DisableSecrets: true,
26+
},
27+
tracker: tracker.CITracker{
28+
FoundFiles: 1,
29+
FoundCountLines: 9,
30+
ParsedCountLines: 9,
31+
ParsedFiles: 1,
32+
LoadedQueries: 146,
33+
ExecutingQueries: 146,
34+
ExecutedQueries: 146,
35+
FailedSimilarityID: 0,
36+
Version: model.Version{
37+
Latest: true,
38+
LatestVersionTag: "Dev",
39+
},
40+
},
41+
scanResults: &Results{
42+
Results: []model.Vulnerability{
43+
{
44+
VulnLines: &[]model.CodeLine{
45+
{
46+
Position: 4,
47+
Line: " metadata:",
48+
},
49+
{
50+
Position: 5,
51+
Line: " name: secret-basic-auth:",
52+
}, {
53+
Position: 6,
54+
Line: " password: \"abcd\"",
55+
},
56+
},
57+
},
58+
},
59+
},
60+
},
61+
}
62+
63+
for _, tt := range tests {
64+
t.Run(tt.name, func(t *testing.T) {
65+
c := Client{}
66+
c.Tracker = &tt.tracker
67+
c.ScanParams = &tt.scanParameters
68+
c.ProBarBuilder = progress.InitializePbBuilder(true, false, true)
69+
c.Printer = printer.NewPrinter(true)
70+
71+
err := c.postScan(tt.scanResults)
72+
require.NoError(t, err)
73+
74+
for _, line := range (*tt.scanResults).Results {
75+
for _, vulnLine := range *line.VulnLines {
76+
if strings.Contains(vulnLine.Line, "password") {
77+
require.Contains(t, vulnLine.Line, "<SECRET-MASKED-ON-PURPOSE>")
78+
}
79+
}
80+
}
81+
82+
})
83+
84+
}
85+
}
86+
87+
func Test_maskSecretsEntropies(t *testing.T) {
88+
tests := []struct {
89+
name string
90+
filename string
91+
scanParameters Parameters
92+
tracker tracker.CITracker
93+
scanResults *Results
94+
}{
95+
{
96+
name: "not print with masked secrets with invalid entropies",
97+
filename: "results",
98+
scanParameters: Parameters{
99+
DisableSecrets: true,
100+
},
101+
tracker: tracker.CITracker{
102+
FoundFiles: 1,
103+
FoundCountLines: 9,
104+
ParsedCountLines: 9,
105+
ParsedFiles: 1,
106+
LoadedQueries: 146,
107+
ExecutingQueries: 146,
108+
ExecutedQueries: 146,
109+
FailedSimilarityID: 0,
110+
Version: model.Version{
111+
Latest: true,
112+
LatestVersionTag: "Dev",
113+
},
114+
},
115+
scanResults: &Results{
116+
Results: []model.Vulnerability{
117+
{
118+
VulnLines: &[]model.CodeLine{
119+
{
120+
Position: 4,
121+
Line: "secret = \"eeeeee\"",
122+
},
123+
},
124+
},
125+
},
126+
},
127+
},
128+
}
129+
130+
for _, tt := range tests {
131+
t.Run(tt.name, func(t *testing.T) {
132+
c := Client{}
133+
c.Tracker = &tt.tracker
134+
c.ScanParams = &tt.scanParameters
135+
c.ProBarBuilder = progress.InitializePbBuilder(true, false, true)
136+
c.Printer = printer.NewPrinter(true)
137+
138+
err := c.postScan(tt.scanResults)
139+
require.NoError(t, err)
140+
141+
for _, line := range (*tt.scanResults).Results {
142+
for _, vulnLine := range *line.VulnLines {
143+
require.NotContains(t, vulnLine.Line, "<SECRET-MASKED-ON-PURPOSE>")
144+
145+
}
146+
}
147+
148+
})
149+
150+
}
151+
}

0 commit comments

Comments
 (0)