From 161663aa58d37c47d6933b900be8b0cd7b463c8b Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:27:26 +0100 Subject: [PATCH 01/12] Fix line content, now correctly handling repeated secrets in same line --- engine/config.go | 1 + engine/engine.go | 10 +-- engine/linecontent/linecontent.go | 16 ++++- engine/linecontent/linecontent_test.go | 84 +++++++++++++++++--------- 4 files changed, 74 insertions(+), 37 deletions(-) diff --git a/engine/config.go b/engine/config.go index 4af1444a..2b0c630f 100644 --- a/engine/config.go +++ b/engine/config.go @@ -37,6 +37,7 @@ var baseConfig = config.Config{ regexp.MustCompile(`verification-metadata\.xml`), regexp.MustCompile(`Database.refactorlog`), regexp.MustCompile(`(?:^|/)\.git$`), + regexp.MustCompile(`(?:^|/)secret\.doc$`), }, }, }, diff --git a/engine/engine.go b/engine/engine.go index d99a65a8..9f696971 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -552,11 +552,6 @@ func buildSecret( } value.Line = strings.ReplaceAll(value.Line, "\r", "") - lineContent, err := linecontent.GetLineContent(value.Line, value.Secret) - if err != nil { - return nil, fmt.Errorf("failed to get line content for source %s: %w", item.GetSource(), err) - } - adjustedStartColumn := value.StartColumn adjustedEndColumn := value.EndColumn if hasNewline { @@ -564,6 +559,11 @@ func buildSecret( adjustedEndColumn-- } + lineContent, err := linecontent.GetLineContent(value.Line, value.Secret, adjustedStartColumn) + if err != nil { + return nil, fmt.Errorf("failed to get line content for source %s: %w", item.GetSource(), err) + } + secret := &secrets.Secret{ ID: findingID, Source: item.GetSource(), diff --git a/engine/linecontent/linecontent.go b/engine/linecontent/linecontent.go index 5cc20c97..23713484 100644 --- a/engine/linecontent/linecontent.go +++ b/engine/linecontent/linecontent.go @@ -11,7 +11,7 @@ const ( contextRightSizeLimit = 250 ) -func GetLineContent(line, secret string) (string, error) { +func GetLineContent(line, secret string, startColumn int) (string, error) { lineSize := len(line) if lineSize == 0 { return "", fmt.Errorf("line empty") @@ -27,8 +27,18 @@ func GetLineContent(line, secret string) (string, error) { lineSize = lineMaxParseSize } - // Find the secret's position in the line - secretStartIndex := strings.Index(line, secret) + // Find the secret's position in the line, searching from the match's start column + // onwards first so that repeated occurrences of secret in the line are disambiguated. + secretStartIndex := -1 + hint := startColumn - 1 + if hint >= 0 && hint < lineSize { + if idx := strings.Index(line[hint:], secret); idx != -1 { + secretStartIndex = hint + idx + } + } + if secretStartIndex == -1 { + secretStartIndex = strings.Index(line, secret) + } if secretStartIndex == -1 { // Secret not found, return truncated content based on context limits maxSize := contextLeftSizeLimit + contextRightSizeLimit diff --git a/engine/linecontent/linecontent_test.go b/engine/linecontent/linecontent_test.go index ba81549e..ad5d31bb 100644 --- a/engine/linecontent/linecontent_test.go +++ b/engine/linecontent/linecontent_test.go @@ -14,6 +14,7 @@ func TestGetLineContent(t *testing.T) { name string line string secret string + startColumn int expected string error bool errorMessage string @@ -59,11 +60,12 @@ func TestGetLineContent(t *testing.T) { error: false, }, { - name: "Secret at the beginning with line size smaller than the parse limit", - line: "start:" + dummySecret + strings.Repeat("A", lineMaxParseSize/2), - secret: dummySecret, - expected: "start:" + dummySecret + strings.Repeat("A", contextRightSizeLimit), - error: false, + name: "Secret at the beginning with line size smaller than the parse limit", + line: "start:" + dummySecret + strings.Repeat("A", lineMaxParseSize/2), + secret: dummySecret, + startColumn: len("start:") + 1, + expected: "start:" + dummySecret + strings.Repeat("A", contextRightSizeLimit), + error: false, }, { name: "Secret found in middle with line size smaller than the parse limit", @@ -74,43 +76,67 @@ func TestGetLineContent(t *testing.T) { "A", contextRightSizeLimit, ) + "end", - secret: dummySecret, - expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", contextRightSizeLimit), - error: false, + secret: dummySecret, + startColumn: len("start") + contextLeftSizeLimit + 1, + expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", contextRightSizeLimit), + error: false, }, { - name: "Secret at the end with line size smaller than the parse limit", - line: strings.Repeat("A", lineMaxParseSize/2) + dummySecret + ":end", - secret: dummySecret, - expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + ":end", - error: false, + name: "Secret at the end with line size smaller than the parse limit", + line: strings.Repeat("A", lineMaxParseSize/2) + dummySecret + ":end", + secret: dummySecret, + startColumn: lineMaxParseSize/2 + 1, + expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + ":end", + error: false, }, { - name: "Secret at the beginning with line size larger than the parse limit", - line: "start:" + dummySecret + strings.Repeat("A", lineMaxParseSize), - secret: dummySecret, - expected: "start:" + dummySecret + strings.Repeat("A", contextRightSizeLimit), - error: false, + name: "Secret at the beginning with line size larger than the parse limit", + line: "start:" + dummySecret + strings.Repeat("A", lineMaxParseSize), + secret: dummySecret, + startColumn: len("start:") + 1, + expected: "start:" + dummySecret + strings.Repeat("A", contextRightSizeLimit), + error: false, }, { - name: "Secret found in middle with line size larger than the parse limit", - line: "start" + strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", lineMaxParseSize) + "end", - secret: dummySecret, - expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", contextRightSizeLimit), - error: false, + name: "Secret found in middle with line size larger than the parse limit", + line: "start" + strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", lineMaxParseSize) + "end", + secret: dummySecret, + startColumn: len("start") + contextLeftSizeLimit + 1, + expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", contextRightSizeLimit), + error: false, }, { - name: "Secret at the end with line size larger than the parse limit", - line: strings.Repeat("A", lineMaxParseSize-100) + dummySecret + strings.Repeat("A", lineMaxParseSize), - secret: dummySecret, - expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", 100-len(dummySecret)), - error: false, + name: "Secret at the end with line size larger than the parse limit", + line: strings.Repeat("A", lineMaxParseSize-100) + dummySecret + strings.Repeat("A", lineMaxParseSize), + secret: dummySecret, + startColumn: lineMaxParseSize - 100 + 1, + expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", 100-len(dummySecret)), + error: false, + }, + { + name: "Secret repeated on the same line uses the occurrence at startColumn", + line: strings.Repeat("a", contextLeftSizeLimit) + dummySecret + strings.Repeat("b", contextLeftSizeLimit+contextRightSizeLimit) + dummySecret + strings.Repeat("c", contextRightSizeLimit), + secret: dummySecret, + startColumn: contextLeftSizeLimit + len(dummySecret) + contextLeftSizeLimit + contextRightSizeLimit + 1, + expected: strings.Repeat("b", contextLeftSizeLimit) + dummySecret + strings.Repeat("c", contextRightSizeLimit), + error: false, + }, + { + name: "Secret repeated with startColumn pointing at the containing match, not the secret itself", + line: strings.Repeat("a", contextLeftSizeLimit) + "KEY=" + dummySecret + strings.Repeat( + "b", + contextLeftSizeLimit+contextRightSizeLimit, + ) + "KEY=" + dummySecret + strings.Repeat("c", contextRightSizeLimit), + secret: dummySecret, + startColumn: contextLeftSizeLimit + len("KEY=") + len(dummySecret) + contextLeftSizeLimit + contextRightSizeLimit + 1, + expected: strings.Repeat("b", contextLeftSizeLimit-len("KEY=")) + "KEY=" + dummySecret + strings.Repeat("c", contextRightSizeLimit), + error: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GetLineContent(tt.line, tt.secret) + got, err := GetLineContent(tt.line, tt.secret, tt.startColumn) if (err != nil) != tt.error { t.Fatalf("GetLineContent() error = %v, wantErr %v", err, tt.error) } From 817f44a6fb53defa43ee3974ab6c130e3a76b234 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:22:13 +0100 Subject: [PATCH 02/12] Adding support for line contents bigger than 10K bytes --- engine/linecontent/linecontent.go | 11 +++++++++-- engine/linecontent/linecontent_test.go | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/engine/linecontent/linecontent.go b/engine/linecontent/linecontent.go index 23713484..db816bd4 100644 --- a/engine/linecontent/linecontent.go +++ b/engine/linecontent/linecontent.go @@ -21,10 +21,17 @@ func GetLineContent(line, secret string, startColumn int) (string, error) { return "", fmt.Errorf("secret empty") } - // Truncate lineContent to max size + // Truncate lineContent to max size, centering the window on startColumn so a secret + // far into a very long line isn't truncated away before it can be found. if lineSize > lineMaxParseSize { - line = line[:lineMaxParseSize] + windowStart := 0 + if hint := startColumn - 1; hint >= 0 && hint < lineSize { + windowStart = max(hint-lineMaxParseSize/2, 0) + windowStart = min(windowStart, lineSize-lineMaxParseSize) + } + line = line[windowStart : windowStart+lineMaxParseSize] lineSize = lineMaxParseSize + startColumn -= windowStart } // Find the secret's position in the line, searching from the match's start column diff --git a/engine/linecontent/linecontent_test.go b/engine/linecontent/linecontent_test.go index ad5d31bb..f7a2fa08 100644 --- a/engine/linecontent/linecontent_test.go +++ b/engine/linecontent/linecontent_test.go @@ -110,7 +110,7 @@ func TestGetLineContent(t *testing.T) { line: strings.Repeat("A", lineMaxParseSize-100) + dummySecret + strings.Repeat("A", lineMaxParseSize), secret: dummySecret, startColumn: lineMaxParseSize - 100 + 1, - expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", 100-len(dummySecret)), + expected: strings.Repeat("A", contextLeftSizeLimit) + dummySecret + strings.Repeat("A", contextRightSizeLimit), error: false, }, { @@ -132,6 +132,20 @@ func TestGetLineContent(t *testing.T) { expected: strings.Repeat("b", contextLeftSizeLimit-len("KEY=")) + "KEY=" + dummySecret + strings.Repeat("c", contextRightSizeLimit), error: false, }, + { + name: "Secret far beyond the parse limit is not truncated away", + line: strings.Repeat("x", lineMaxParseSize*3) + "PRE" + dummySecret + "POST" + strings.Repeat( + "y", + lineMaxParseSize, + ), + secret: dummySecret, + startColumn: lineMaxParseSize*3 + len("PRE") + 1, + expected: strings.Repeat("x", contextLeftSizeLimit-len("PRE")) + "PRE" + dummySecret + "POST" + strings.Repeat( + "y", + contextRightSizeLimit-len("POST"), + ), + error: false, + }, } for _, tt := range tests { From 876541e1f5d9aea74757fd384deaa4d4d964624d Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Thu, 3 Sep 2026 17:35:18 +0100 Subject: [PATCH 03/12] Another fix to line content. Exporting RegexSuffix --- engine/linecontent/linecontent.go | 23 ++++++++++------------- engine/rules/ruledefine/utils.go | 6 +++--- pkg/rules.go | 2 ++ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/engine/linecontent/linecontent.go b/engine/linecontent/linecontent.go index db816bd4..c34dc224 100644 --- a/engine/linecontent/linecontent.go +++ b/engine/linecontent/linecontent.go @@ -21,12 +21,13 @@ func GetLineContent(line, secret string, startColumn int) (string, error) { return "", fmt.Errorf("secret empty") } - // Truncate lineContent to max size, centering the window on startColumn so a secret - // far into a very long line isn't truncated away before it can be found. + // For lines > lineMaxParseSize, get just the necessary context around the secret if lineSize > lineMaxParseSize { + matchStartIndex := startColumn - 1 windowStart := 0 - if hint := startColumn - 1; hint >= 0 && hint < lineSize { - windowStart = max(hint-lineMaxParseSize/2, 0) + if matchStartIndex >= 0 && matchStartIndex < lineSize { + windowStart = max(matchStartIndex-lineMaxParseSize/2, 0) + // adjust line context window if windowStart+lineMaxParseSize would be higher than lineSize windowStart = min(windowStart, lineSize-lineMaxParseSize) } line = line[windowStart : windowStart+lineMaxParseSize] @@ -34,18 +35,14 @@ func GetLineContent(line, secret string, startColumn int) (string, error) { startColumn -= windowStart } - // Find the secret's position in the line, searching from the match's start column - // onwards first so that repeated occurrences of secret in the line are disambiguated. + // The same secret value can appear more than once on a line. Search for it at the relevant index for this secret instance secretStartIndex := -1 - hint := startColumn - 1 - if hint >= 0 && hint < lineSize { - if idx := strings.Index(line[hint:], secret); idx != -1 { - secretStartIndex = hint + idx + matchStartIndex := startColumn - 1 + if matchStartIndex >= 0 && matchStartIndex < lineSize { + if idx := strings.Index(line[matchStartIndex:], secret); idx != -1 { + secretStartIndex = matchStartIndex + idx } } - if secretStartIndex == -1 { - secretStartIndex = strings.Index(line, secret) - } if secretStartIndex == -1 { // Secret not found, return truncated content based on context limits maxSize := contextLeftSizeLimit + contextRightSizeLimit diff --git a/engine/rules/ruledefine/utils.go b/engine/rules/ruledefine/utils.go index ec60d7de..9a0d958c 100644 --- a/engine/rules/ruledefine/utils.go +++ b/engine/rules/ruledefine/utils.go @@ -27,7 +27,7 @@ const ( // \x60 = ` secretPrefixUnique = `\b(` secretPrefix = `[\x60'"\s=]{0,20}(` //nolint:gosec // This is a regex pattern - secretSuffix = `)(?:[\x60'"\s;]|\\[nr]|$)` //nolint:gosec // This is a regex pattern + SecretSuffix = `)(?:[\x60'"\s;]|\\[nr]|$)` //nolint:gosec // This is a regex pattern secretSuffixIncludingXml = `)(?:['|\"|\n|\r|\s|\x60|;]|\\n|\\r|$|\s{0,10}<\/string>)` //nolint:gosec // This is a regex pattern ) @@ -46,7 +46,7 @@ func generateSemiGenericRegex(identifiers []string, secretRegex string, isCaseIn sb.WriteString(operator) sb.WriteString(secretPrefix) sb.WriteString(secretRegex) - sb.WriteString(secretSuffix) + sb.WriteString(SecretSuffix) return regexp.MustCompile(sb.String()) } @@ -63,7 +63,7 @@ func generateUniqueTokenRegex(secretRegex string, isCaseInsensitive bool) *regex } sb.WriteString(secretPrefixUnique) sb.WriteString(secretRegex) - sb.WriteString(secretSuffix) + sb.WriteString(SecretSuffix) return regexp.MustCompile(sb.String()) } diff --git a/pkg/rules.go b/pkg/rules.go index 87ead50c..e7da1aeb 100644 --- a/pkg/rules.go +++ b/pkg/rules.go @@ -8,3 +8,5 @@ import ( func GetDefaultRules(includeDeprecated bool) []*ruledefine.Rule { return rules.GetDefaultRules(includeDeprecated) } + +func GetRegexSuffix() string { return ruledefine.SecretSuffix } From 6d7c099fe14b31904a45b12fee67c8cdeba046f0 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:11:25 +0100 Subject: [PATCH 04/12] Remove change from allowLists --- engine/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/config.go b/engine/config.go index 2b0c630f..4af1444a 100644 --- a/engine/config.go +++ b/engine/config.go @@ -37,7 +37,6 @@ var baseConfig = config.Config{ regexp.MustCompile(`verification-metadata\.xml`), regexp.MustCompile(`Database.refactorlog`), regexp.MustCompile(`(?:^|/)\.git$`), - regexp.MustCompile(`(?:^|/)secret\.doc$`), }, }, }, From 974dabc4a6391c9d439e974a56d18e32b6ea7b05 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:42:08 +0100 Subject: [PATCH 05/12] Fix trivy issues --- Dockerfile | 4 ++-- go.mod | 8 ++++---- go.sum | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4d582405..399da46e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # and "Missing User Instruction" since 2ms container is stopped after scan # Builder image -FROM checkmarx/go:1.26.4-r0-cb8702a93db4a4@sha256:cb8702a93db4a4b07da9c6e6e93bf412091e7b121b6ade95aa4228ec5fae4301 AS builder +FROM checkmarx/go:1.27.0-r1-7ff3a27a305109@sha256:7ff3a27a305109341ebf351a1421172d7ee41aeeeb0609451ddb6c8ee5d144b3 AS builder WORKDIR /app @@ -20,7 +20,7 @@ COPY . . RUN GOOS=linux GOARCH=amd64 go build -buildvcs=false -ldflags="-s -w" -a -o /app/2ms . # Runtime image -FROM checkmarx/git:2.55.0-r4-17886d1320eb5b@sha256:17886d1320eb5b3370f1c4bc4e3f95b5005cd25058ae254d051a85dcde76c33c +FROM checkmarx/git:2.55.0-r5-d0ccbb0b82fcb8@sha256:d0ccbb0b82fcb8c84ee36087b47eefbf59f8259c4f902fbb3591acd1ee00c546 WORKDIR /app diff --git a/go.mod b/go.mod index e0c068cd..90cbc670 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/checkmarx/2ms/v5 -go 1.26.4 +go 1.27.0 replace ( golang.org/x/oauth2 => golang.org/x/oauth2 v0.30.0 @@ -29,9 +29,9 @@ require ( github.com/zricethezav/gitleaks/v8 v8.28.0 go.uber.org/mock v0.5.2 golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa - golang.org/x/net v0.56.0 + golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 - golang.org/x/text v0.40.0 + golang.org/x/text v0.41.0 golang.org/x/time v0.5.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -88,6 +88,6 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/crypto v0.54.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/go.sum b/go.sum index dcb1aaf5..3fc6e89a 100644 --- a/go.sum +++ b/go.sum @@ -1252,8 +1252,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1387,8 +1387,8 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1558,8 +1558,8 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 3e89092bb25d525600e2bfbf3c9d6128dd17bfb5 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:03:12 +0100 Subject: [PATCH 06/12] Review change to GetRegexSuffix --- pkg/rules.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/rules.go b/pkg/rules.go index e7da1aeb..567fb94a 100644 --- a/pkg/rules.go +++ b/pkg/rules.go @@ -9,4 +9,6 @@ func GetDefaultRules(includeDeprecated bool) []*ruledefine.Rule { return rules.GetDefaultRules(includeDeprecated) } -func GetRegexSuffix() string { return ruledefine.SecretSuffix } +func GetRegexSuffix() string { + return ruledefine.SecretSuffix +} From ae8f9dbecd7e731697fc87088d013e08d08713a6 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:11:26 +0100 Subject: [PATCH 07/12] Update unit test that was failing due to update to go 1.27 --- cmd/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/config_test.go b/cmd/config_test.go index 3c0a8477..f33a0f71 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -350,7 +350,7 @@ func TestCustomRulesFlag(t *testing.T) { name: "Invalid rule type", customRulesFile: "testData/customRulesInvalidRuleType.json", expectedRules: nil, - expectErrors: []error{fmt.Errorf("cannot unmarshal number -2 into Go struct field Rule.scoreRuleType of type uint8")}, + expectErrors: []error{fmt.Errorf("cannot unmarshal number -2"), fmt.Errorf("scoreRuleType")}, }, } From a3aa9a38d101712ab7f94a58947c6a06fb446e49 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:21:17 +0100 Subject: [PATCH 08/12] Remove export of GetRegexSuffix --- pkg/rules.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/rules.go b/pkg/rules.go index 567fb94a..87ead50c 100644 --- a/pkg/rules.go +++ b/pkg/rules.go @@ -8,7 +8,3 @@ import ( func GetDefaultRules(includeDeprecated bool) []*ruledefine.Rule { return rules.GetDefaultRules(includeDeprecated) } - -func GetRegexSuffix() string { - return ruledefine.SecretSuffix -} From 7ffc56ebdb3c3b5128e63a5a050279d6386d851c Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:09:56 +0100 Subject: [PATCH 09/12] remove suffix characters from end column calculation --- engine/engine.go | 44 +++++++++++++++++++++++++- engine/engine_test.go | 53 +++++++++++++++++++++++++++++++- engine/rules/ruledefine/utils.go | 4 +-- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 9f696971..572e2a18 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -527,6 +527,43 @@ func GetRulesCommand(engineConfig *EngineConfig) *cobra.Command { } } +// secretSuffixTailRegexes match, at the end of a string, whatever a rule's secret-suffix +// regex would have matched right after the secret's capture group. They're derived directly +// from ruledefine.SecretSuffix and ruledefine.SecretSuffixIncludingXml (stripping the capture +// group's closing paren and anchoring to the end) rather than duplicating their character +// classes, so they can't drift out of sync with the source patterns. Both are checked +// independently -- rather than relying on SecretSuffixIncludingXml always being a superset of +// SecretSuffix -- so a future divergence between the two can't hide a real overlap. +var secretSuffixTailRegexes = []*regexp.Regexp{ + regexp.MustCompile(strings.TrimPrefix(ruledefine.SecretSuffix, ")") + "$"), + regexp.MustCompile(strings.TrimPrefix(ruledefine.SecretSuffixIncludingXml, ")") + "$"), +} + +// trimSecretSuffixOverlap returns endColumn adjusted so it no longer includes the trailing +// boundary characters matched by the rule's secret-suffix regex, which are not part of the +// secret itself. line and endColumn must correspond to each other, i.e. endColumn must be a +// valid 1-based, inclusive column within line. +// +// Both suffix regexes always have a zero-width `$` alternative, so they'll always "match" at +// endColumn itself; what matters is the longest overlap found across both regexes, not merely +// whether one of them matched. +func trimSecretSuffixOverlap(line string, endColumn int) int { + if endColumn <= 0 || endColumn > len(line) { + return endColumn + } + head := line[:endColumn] + + overlap := 0 + for _, re := range secretSuffixTailRegexes { + if loc := re.FindStringIndex(head); loc != nil { + if matched := loc[1] - loc[0]; matched > overlap { + overlap = matched + } + } + } + return endColumn - overlap +} + // buildSecret creates a secret object from the given source item and finding func buildSecret( ctx context.Context, @@ -547,13 +584,18 @@ func buildSecret( hasNewline := strings.HasPrefix(value.Line, "\n") + // Rule regexes (ruledefine.SecretSuffix / secretSuffixIncludingXml) include a + // trailing boundary match after the secret's capture group, so EndColumn as reported + // by the detector can extend past the secret's actual last character. Trim that + // overlap before it's used for anything else below. + adjustedEndColumn := trimSecretSuffixOverlap(value.Line, value.EndColumn) + if hasNewline { value.Line = strings.TrimPrefix(value.Line, "\n") } value.Line = strings.ReplaceAll(value.Line, "\r", "") adjustedStartColumn := value.StartColumn - adjustedEndColumn := value.EndColumn if hasNewline { adjustedStartColumn-- adjustedEndColumn-- diff --git a/engine/engine_test.go b/engine/engine_test.go index 0fc7cabb..aa8c4156 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -476,9 +476,30 @@ func TestDetectChunks(t *testing.T) { func TestSecretsColumnIndex(t *testing.T) { + const defaultSecret = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + + // True positive from ruledefine's generic-api-key (Generic-Api-Key / GenericCredential) + // rule, which is built with generateSemiGenericRegexIncludingXml. Its secret-suffix, + // SecretSuffixIncludingXml, matches "" right after the secret with no + // whitespace in between (see engine/rules/ruledefine/generic_credential_test.go). + xmlSuffixSecret := "AIzaSyATDL7Wz3Ze6BU31Yv3fVVth30Skyib29g" + xmlSuffixLine := "" + xmlSuffixSecret + "" + xmlSuffixSecretStart := len("") + 1 + xmlSuffixSecretEnd := xmlSuffixSecretStart + len(xmlSuffixSecret) - 1 + + // True positive from ruledefine's Adafruit API Key rule, which is built with the plain + // generateSemiGenericRegex/SecretSuffix. The secret sits inside a JSON string value, so + // it's followed by a literal (2-character) "\n" escape sequence, not an actual newline + // byte (see engine/rules/ruledefine/adafruit_test.go). + escapedNewlineSecret := "5qnwhukyv3wi7h9etbfrswi6l8yiwhjl" + escapedNewlineLine := `{"config.ini": "ADAFRUIT_TOKEN=` + escapedNewlineSecret + `\nBACKUP_ENABLED=true"}` + escapedNewlineSecretStart := strings.Index(escapedNewlineLine, escapedNewlineSecret) + 1 + escapedNewlineSecretEnd := escapedNewlineSecretStart + len(escapedNewlineSecret) - 1 + tests := []struct { name string lineContent string + secret string startColumn int endColumn int expectedLineContent string @@ -488,6 +509,7 @@ func TestSecretsColumnIndex(t *testing.T) { { name: "secret on first line without newline", lineContent: `let apikey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"`, + secret: defaultSecret, startColumn: 14, endColumn: 50, expectedLineContent: `let apikey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"`, @@ -497,6 +519,7 @@ func TestSecretsColumnIndex(t *testing.T) { { name: "secret with leading newline", lineContent: "\nlet apikey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"", + secret: defaultSecret, startColumn: 15, endColumn: 51, expectedLineContent: `let apikey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"`, @@ -506,6 +529,7 @@ func TestSecretsColumnIndex(t *testing.T) { { name: "leading newline followed by tab indentation", lineContent: "\n let apikey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"", + secret: defaultSecret, startColumn: 2, endColumn: 7, expectedLineContent: " let apikey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"", @@ -515,6 +539,7 @@ func TestSecretsColumnIndex(t *testing.T) { { name: "leading newline followed by tab indentation with special character", lineContent: "\n\tlet apikey€ = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"", + secret: defaultSecret, startColumn: 2, endColumn: 7, expectedLineContent: " let apikey€ = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"", @@ -524,12 +549,38 @@ func TestSecretsColumnIndex(t *testing.T) { { name: "newline with content larger than context limit", lineContent: "\n" + strings.Repeat("A", 500) + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + strings.Repeat("B", 500), + secret: defaultSecret, startColumn: 501, endColumn: 536, expectedLineContent: strings.Repeat("A", 250) + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + strings.Repeat("B", 250), expectedStartColumn: 500, expectedEndColumn: 535, }, + { + // EndColumn as reported by the detector includes the "" that + // SecretSuffixIncludingXml matched after the secret; buildSecret must trim it + // back to the secret's actual last character. + name: "generic-api-key xml suffix consumes closing tag", + lineContent: xmlSuffixLine, + secret: xmlSuffixSecret, + startColumn: xmlSuffixSecretStart, + endColumn: xmlSuffixSecretEnd + len(""), + expectedLineContent: xmlSuffixLine, + expectedStartColumn: xmlSuffixSecretStart, + expectedEndColumn: xmlSuffixSecretEnd, + }, + { + // EndColumn as reported by the detector includes the literal two-character + // "\n" that SecretSuffix's `\\[nr]` alternative matched after the secret. + name: "adafruit key followed by literal backslash-n", + lineContent: escapedNewlineLine, + secret: escapedNewlineSecret, + startColumn: escapedNewlineSecretStart, + endColumn: escapedNewlineSecretEnd + len(`\n`), + expectedLineContent: escapedNewlineLine, + expectedStartColumn: escapedNewlineSecretStart, + expectedEndColumn: escapedNewlineSecretEnd, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -539,7 +590,7 @@ func TestSecretsColumnIndex(t *testing.T) { finding := report.Finding{ StartColumn: tt.startColumn, EndColumn: tt.endColumn, - Secret: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", + Secret: tt.secret, RuleID: "test-rule", Description: "Test Description", Line: tt.lineContent, diff --git a/engine/rules/ruledefine/utils.go b/engine/rules/ruledefine/utils.go index 9a0d958c..ce210a9f 100644 --- a/engine/rules/ruledefine/utils.go +++ b/engine/rules/ruledefine/utils.go @@ -28,7 +28,7 @@ const ( secretPrefixUnique = `\b(` secretPrefix = `[\x60'"\s=]{0,20}(` //nolint:gosec // This is a regex pattern SecretSuffix = `)(?:[\x60'"\s;]|\\[nr]|$)` //nolint:gosec // This is a regex pattern - secretSuffixIncludingXml = `)(?:['|\"|\n|\r|\s|\x60|;]|\\n|\\r|$|\s{0,10}<\/string>)` //nolint:gosec // This is a regex pattern + SecretSuffixIncludingXml = `)(?:['|\"|\n|\r|\s|\x60|;]|\\n|\\r|$|\s{0,10}<\/string>)` //nolint:gosec // This is a regex pattern ) func generateSemiGenericRegex(identifiers []string, secretRegex string, isCaseInsensitive bool) *regexp.Regexp { @@ -83,7 +83,7 @@ func generateSemiGenericRegexIncludingXml(identifiers []string, secretRegex stri sb.WriteString(operator) sb.WriteString(secretPrefix) sb.WriteString(secretRegex) - sb.WriteString(secretSuffixIncludingXml) + sb.WriteString(SecretSuffixIncludingXml) return regexp.MustCompile(sb.String()) } From c56626c0841b655aa0024fcd0d2ceecccef6bae8 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:08:33 +0100 Subject: [PATCH 10/12] Updated tests --- engine/engine_test.go | 86 +++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/engine/engine_test.go b/engine/engine_test.go index aa8c4156..6094e260 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -478,23 +478,12 @@ func TestSecretsColumnIndex(t *testing.T) { const defaultSecret = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" - // True positive from ruledefine's generic-api-key (Generic-Api-Key / GenericCredential) - // rule, which is built with generateSemiGenericRegexIncludingXml. Its secret-suffix, - // SecretSuffixIncludingXml, matches "" right after the secret with no - // whitespace in between (see engine/rules/ruledefine/generic_credential_test.go). - xmlSuffixSecret := "AIzaSyATDL7Wz3Ze6BU31Yv3fVVth30Skyib29g" + xmlSuffixSecret := "AIzaSyATD" xmlSuffixLine := "" + xmlSuffixSecret + "" xmlSuffixSecretStart := len("") + 1 xmlSuffixSecretEnd := xmlSuffixSecretStart + len(xmlSuffixSecret) - 1 - // True positive from ruledefine's Adafruit API Key rule, which is built with the plain - // generateSemiGenericRegex/SecretSuffix. The secret sits inside a JSON string value, so - // it's followed by a literal (2-character) "\n" escape sequence, not an actual newline - // byte (see engine/rules/ruledefine/adafruit_test.go). - escapedNewlineSecret := "5qnwhukyv3wi7h9etbfrswi6l8yiwhjl" - escapedNewlineLine := `{"config.ini": "ADAFRUIT_TOKEN=` + escapedNewlineSecret + `\nBACKUP_ENABLED=true"}` - escapedNewlineSecretStart := strings.Index(escapedNewlineLine, escapedNewlineSecret) + 1 - escapedNewlineSecretEnd := escapedNewlineSecretStart + len(escapedNewlineSecret) - 1 + generalSuffixSecret := "5qnwhuk" tests := []struct { name string @@ -570,16 +559,67 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: xmlSuffixSecretEnd, }, { - // EndColumn as reported by the detector includes the literal two-character - // "\n" that SecretSuffix's `\\[nr]` alternative matched after the secret. - name: "adafruit key followed by literal backslash-n", - lineContent: escapedNewlineLine, - secret: escapedNewlineSecret, - startColumn: escapedNewlineSecretStart, - endColumn: escapedNewlineSecretEnd + len(`\n`), - expectedLineContent: escapedNewlineLine, - expectedStartColumn: escapedNewlineSecretStart, - expectedEndColumn: escapedNewlineSecretEnd, + // EndColumn as reported by the detector includes the trailing real carriage + // return that SecretSuffix's `\s` alternative matched after the secret. + name: "secret followed by carriage return", + lineContent: generalSuffixSecret + "\r", + secret: generalSuffixSecret, + startColumn: 1, + endColumn: len(generalSuffixSecret + "\r"), + expectedLineContent: generalSuffixSecret, // buildSecret strips all \r bytes from Line + expectedStartColumn: 1, + expectedEndColumn: len(generalSuffixSecret), + }, + { + // EndColumn as reported by the detector includes the trailing real newline + // that SecretSuffix's `\s` alternative matched after the secret. + name: "secret followed by newline", + lineContent: generalSuffixSecret + "\n", + secret: generalSuffixSecret, + startColumn: 1, + endColumn: len(generalSuffixSecret + "\n"), + expectedLineContent: generalSuffixSecret + "\n", + expectedStartColumn: 1, + expectedEndColumn: len(generalSuffixSecret), + }, + { + // EndColumn as reported by the detector includes the trailing ";" that + // SecretSuffix's character-class alternative matched after the secret. + name: "secret followed by semicolon", + lineContent: generalSuffixSecret + ";", + secret: generalSuffixSecret, + startColumn: 1, + endColumn: len(generalSuffixSecret + ";"), + expectedLineContent: generalSuffixSecret + ";", + expectedStartColumn: 1, + expectedEndColumn: len(generalSuffixSecret), + }, + { + // EndColumn as reported by the detector includes the trailing quote that + // SecretSuffix's character-class alternative matched after the secret. + name: `secret followed by double quote`, + lineContent: generalSuffixSecret + `"`, + secret: generalSuffixSecret, + startColumn: 1, + endColumn: len(generalSuffixSecret + `"`), + expectedLineContent: generalSuffixSecret + `"`, + expectedStartColumn: 1, + expectedEndColumn: len(generalSuffixSecret), + }, + { + // EndColumn as reported by the detector includes only the last suffix unit + // matched: SecretSuffix's suffix group is matched once (not repeated), so of + // the two literal escapes here, only the trailing "\n" is ever consumed by the + // detector -- and so only it gets trimmed. The earlier literal "\r" escape is + // left counted in EndColumn; this is a known limitation, not the ideal result. + name: `secret followed by literal backslash-r backslash-n`, + lineContent: generalSuffixSecret + `\r\n`, + secret: generalSuffixSecret, + startColumn: 1, + endColumn: len(generalSuffixSecret + `\r\n`), + expectedLineContent: generalSuffixSecret + `\r\n`, + expectedStartColumn: 1, + expectedEndColumn: len(generalSuffixSecret + `\r`), }, } for _, tt := range tests { From c526f51cbae1495e88da4c4533fd8d3248ef2a80 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:20:54 +0100 Subject: [PATCH 11/12] Delete spurious comments --- engine/engine.go | 22 +++++++--------------- engine/engine_test.go | 16 ---------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 572e2a18..6c2e330b 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -529,20 +529,14 @@ func GetRulesCommand(engineConfig *EngineConfig) *cobra.Command { // secretSuffixTailRegexes match, at the end of a string, whatever a rule's secret-suffix // regex would have matched right after the secret's capture group. They're derived directly -// from ruledefine.SecretSuffix and ruledefine.SecretSuffixIncludingXml (stripping the capture -// group's closing paren and anchoring to the end) rather than duplicating their character -// classes, so they can't drift out of sync with the source patterns. Both are checked -// independently -- rather than relying on SecretSuffixIncludingXml always being a superset of -// SecretSuffix -- so a future divergence between the two can't hide a real overlap. +// from ruledefine.SecretSuffix and ruledefine.SecretSuffixIncludingXml var secretSuffixTailRegexes = []*regexp.Regexp{ regexp.MustCompile(strings.TrimPrefix(ruledefine.SecretSuffix, ")") + "$"), regexp.MustCompile(strings.TrimPrefix(ruledefine.SecretSuffixIncludingXml, ")") + "$"), } // trimSecretSuffixOverlap returns endColumn adjusted so it no longer includes the trailing -// boundary characters matched by the rule's secret-suffix regex, which are not part of the -// secret itself. line and endColumn must correspond to each other, i.e. endColumn must be a -// valid 1-based, inclusive column within line. +// boundary characters matched by the rule's secret-suffix regex. // // Both suffix regexes always have a zero-width `$` alternative, so they'll always "match" at // endColumn itself; what matters is the longest overlap found across both regexes, not merely @@ -555,9 +549,11 @@ func trimSecretSuffixOverlap(line string, endColumn int) int { overlap := 0 for _, re := range secretSuffixTailRegexes { - if loc := re.FindStringIndex(head); loc != nil { - if matched := loc[1] - loc[0]; matched > overlap { - overlap = matched + matches := re.FindStringIndex(head) + if matches != nil { + matchedSuffixLength := matches[1] - matches[0] + if matchedSuffixLength > overlap { + overlap = matchedSuffixLength } } } @@ -584,10 +580,6 @@ func buildSecret( hasNewline := strings.HasPrefix(value.Line, "\n") - // Rule regexes (ruledefine.SecretSuffix / secretSuffixIncludingXml) include a - // trailing boundary match after the secret's capture group, so EndColumn as reported - // by the detector can extend past the secret's actual last character. Trim that - // overlap before it's used for anything else below. adjustedEndColumn := trimSecretSuffixOverlap(value.Line, value.EndColumn) if hasNewline { diff --git a/engine/engine_test.go b/engine/engine_test.go index 6094e260..30e3ed49 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -546,9 +546,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: 535, }, { - // EndColumn as reported by the detector includes the "" that - // SecretSuffixIncludingXml matched after the secret; buildSecret must trim it - // back to the secret's actual last character. name: "generic-api-key xml suffix consumes closing tag", lineContent: xmlSuffixLine, secret: xmlSuffixSecret, @@ -559,8 +556,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: xmlSuffixSecretEnd, }, { - // EndColumn as reported by the detector includes the trailing real carriage - // return that SecretSuffix's `\s` alternative matched after the secret. name: "secret followed by carriage return", lineContent: generalSuffixSecret + "\r", secret: generalSuffixSecret, @@ -571,8 +566,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: len(generalSuffixSecret), }, { - // EndColumn as reported by the detector includes the trailing real newline - // that SecretSuffix's `\s` alternative matched after the secret. name: "secret followed by newline", lineContent: generalSuffixSecret + "\n", secret: generalSuffixSecret, @@ -583,8 +576,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: len(generalSuffixSecret), }, { - // EndColumn as reported by the detector includes the trailing ";" that - // SecretSuffix's character-class alternative matched after the secret. name: "secret followed by semicolon", lineContent: generalSuffixSecret + ";", secret: generalSuffixSecret, @@ -595,8 +586,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: len(generalSuffixSecret), }, { - // EndColumn as reported by the detector includes the trailing quote that - // SecretSuffix's character-class alternative matched after the secret. name: `secret followed by double quote`, lineContent: generalSuffixSecret + `"`, secret: generalSuffixSecret, @@ -607,11 +596,6 @@ func TestSecretsColumnIndex(t *testing.T) { expectedEndColumn: len(generalSuffixSecret), }, { - // EndColumn as reported by the detector includes only the last suffix unit - // matched: SecretSuffix's suffix group is matched once (not repeated), so of - // the two literal escapes here, only the trailing "\n" is ever consumed by the - // detector -- and so only it gets trimmed. The earlier literal "\r" escape is - // left counted in EndColumn; this is a known limitation, not the ideal result. name: `secret followed by literal backslash-r backslash-n`, lineContent: generalSuffixSecret + `\r\n`, secret: generalSuffixSecret, From 29398fbd269e010f5871be16da150fe027b7be77 Mon Sep 17 00:00:00 2001 From: Diogo Rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 16 Sep 2026 14:19:21 +0100 Subject: [PATCH 12/12] Update dockerfile tags --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 399da46e..9211c54d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # and "Missing User Instruction" since 2ms container is stopped after scan # Builder image -FROM checkmarx/go:1.27.0-r1-7ff3a27a305109@sha256:7ff3a27a305109341ebf351a1421172d7ee41aeeeb0609451ddb6c8ee5d144b3 AS builder +FROM checkmarx/go:1.27.1-r0-424cf19b9e848d@sha256:424cf19b9e848d86bbf0ed45b216d782f064bfb6b1dd7eba7f5a8cc3f750088f AS builder WORKDIR /app @@ -20,7 +20,7 @@ COPY . . RUN GOOS=linux GOARCH=amd64 go build -buildvcs=false -ldflags="-s -w" -a -o /app/2ms . # Runtime image -FROM checkmarx/git:2.55.0-r5-d0ccbb0b82fcb8@sha256:d0ccbb0b82fcb8c84ee36087b47eefbf59f8259c4f902fbb3591acd1ee00c546 +FROM checkmarx/git:2.55.0-r7-193d1e713216b7@sha256:193d1e713216b75b63eb05c3ebac0185620565b10a33d2ca1b3a89e8bd46c4fc WORKDIR /app