Skip to content

Commit 7f4e37d

Browse files
authored
[INS-228] Add ignorePattern configuration support to Postgres and Sqlserver detectors (#4612)
* add ignorePattern configuration support to postgres and sqlserver detectors * add test for sqlserver detector that doesn't use New
1 parent daf5bf1 commit 7f4e37d

4 files changed

Lines changed: 187 additions & 3 deletions

File tree

pkg/detectors/postgres/postgres.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ var (
5555
type Scanner struct {
5656
detectors.DefaultMultiPartCredentialProvider
5757
detectLoopback bool // Automated tests run against localhost, but we want to ignore those results in the wild
58+
ignorePatterns []*regexp.Regexp
59+
}
60+
61+
func New(opts ...func(*Scanner)) *Scanner {
62+
scanner := &Scanner{
63+
ignorePatterns: []*regexp.Regexp{},
64+
}
65+
for _, opt := range opts {
66+
opt(scanner)
67+
}
68+
69+
return scanner
70+
}
71+
72+
func WithIgnorePattern(ignoreStrings []string) func(*Scanner) {
73+
return func(s *Scanner) {
74+
var ignorePatterns []*regexp.Regexp
75+
for _, ignoreString := range ignoreStrings {
76+
ignorePattern, err := regexp.Compile(ignoreString)
77+
if err != nil {
78+
panic(fmt.Sprintf("%s is not a valid regex, error received: %v", ignoreString, err))
79+
}
80+
ignorePatterns = append(ignorePatterns, ignorePattern)
81+
}
82+
83+
s.ignorePatterns = ignorePatterns
84+
}
5885
}
5986

6087
var _ detectors.Detector = (*Scanner)(nil)
@@ -66,7 +93,7 @@ func (s Scanner) Keywords() []string {
6693

6794
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
6895
var results []detectors.Result
69-
candidateParamSets := findUriMatches(data)
96+
candidateParamSets := findUriMatches(data, s.ignorePatterns)
7097

7198
for _, params := range candidateParamSets {
7299
if common.IsDone(ctx) {
@@ -161,9 +188,12 @@ func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {
161188
return false, ""
162189
}
163190

164-
func findUriMatches(data []byte) []map[string]string {
191+
func findUriMatches(data []byte, ignorePatterns []*regexp.Regexp) []map[string]string {
165192
var matches []map[string]string
166193
for _, uri := range uriPattern.FindAll(data, -1) {
194+
if shouldIgnore(uri, ignorePatterns) {
195+
continue
196+
}
167197
// Capture the database type (e.g., "postgres" or "postgresql")
168198
dbTypeMatch := uriPattern.FindSubmatch(uri)
169199
if len(dbTypeMatch) < 2 {
@@ -188,6 +218,15 @@ func findUriMatches(data []byte) []map[string]string {
188218
return matches
189219
}
190220

221+
func shouldIgnore(uri []byte, ignorePatterns []*regexp.Regexp) bool {
222+
for _, ignore := range ignorePatterns {
223+
if ignore.Match(uri) {
224+
return true
225+
}
226+
}
227+
return false
228+
}
229+
191230
// getDeadlineInSeconds gets the deadline from the context in seconds. If there
192231
// is no deadline, false is returned. If the deadline is already exceeded, a
193232
// negative or 0 value will be returned.

pkg/detectors/postgres/postgres_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,18 @@ func TestPostgres_Pattern(t *testing.T) {
8181
})
8282
}
8383
}
84+
85+
func TestPostgres_FromDataWithIgnorePattern(t *testing.T) {
86+
s := New(
87+
WithIgnorePattern([]string{
88+
`1\.2\.3\.4`,
89+
}))
90+
got, err := s.FromData(context.Background(), false, []byte(validUriPattern))
91+
if err != nil {
92+
t.Errorf("FromData() error = %v", err)
93+
return
94+
}
95+
if len(got) != 0 {
96+
t.Errorf("expected no results, but got %d", len(got))
97+
}
98+
}

pkg/detectors/sqlserver/sqlserver.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,35 @@ import (
1818
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1919
)
2020

21-
type Scanner struct{}
21+
type Scanner struct {
22+
ignorePatterns []*regexp.Regexp
23+
}
24+
25+
func New(opts ...func(*Scanner)) *Scanner {
26+
scanner := &Scanner{
27+
ignorePatterns: []*regexp.Regexp{},
28+
}
29+
for _, opt := range opts {
30+
opt(scanner)
31+
}
32+
33+
return scanner
34+
}
35+
36+
func WithIgnorePattern(ignoreStrings []string) func(*Scanner) {
37+
return func(s *Scanner) {
38+
var ignorePatterns []*regexp.Regexp
39+
for _, ignoreString := range ignoreStrings {
40+
ignorePattern, err := regexp.Compile(ignoreString)
41+
if err != nil {
42+
panic(fmt.Sprintf("%s is not a valid regex, error received: %v", ignoreString, err))
43+
}
44+
ignorePatterns = append(ignorePatterns, ignorePattern)
45+
}
46+
47+
s.ignorePatterns = ignorePatterns
48+
}
49+
}
2250

2351
// Ensure the Scanner satisfies the interface at compile time.
2452
var _ detectors.Detector = (*Scanner)(nil)
@@ -38,6 +66,9 @@ func (s Scanner) Keywords() []string {
3866
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3967
matches := pattern.FindAllStringSubmatch(string(data), -1)
4068
for _, match := range matches {
69+
if shouldIgnore(match[1], s.ignorePatterns) {
70+
continue
71+
}
4172
paramsUnsafe, err := msdsn.Parse(match[1])
4273
if err != nil {
4374
continue
@@ -80,6 +111,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
80111
return results, nil
81112
}
82113

114+
func shouldIgnore(uri string, ignorePatterns []*regexp.Regexp) bool {
115+
for _, ignore := range ignorePatterns {
116+
if ignore.MatchString(uri) {
117+
return true
118+
}
119+
}
120+
return false
121+
}
122+
83123
var ping = func(ctx context.Context, config msdsn.Config) (bool, error) {
84124
// TCP connectivity check to prevent indefinite hangs
85125
address := net.JoinHostPort(config.Host, strconv.Itoa(int(config.Port)))

pkg/detectors/sqlserver/sqlserver_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package sqlserver
22

33
import (
4+
"context"
5+
"fmt"
46
"testing"
7+
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
10+
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
511
)
612

713
func TestSQLServer_Pattern(t *testing.T) {
@@ -18,3 +24,87 @@ func TestSQLServer_Pattern(t *testing.T) {
1824
t.Errorf("SQLServer.pattern: did not find connection string in xml format")
1925
}
2026
}
27+
28+
const (
29+
validConnStr = `builder.Services.AddDbContext<Database>(optionsBuilder => optionsBuilder.UseSqlServer("Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;"));`
30+
validParsedStr = `sqlserver://sa:P%40ssw0rd%21@localhost?database=master&dial+timeout=15&disableretry=false`
31+
invalidConnStr = `some random text without connection string`
32+
)
33+
34+
func TestSqlServer_Pattern(t *testing.T) {
35+
d := Scanner{}
36+
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
37+
tests := []struct {
38+
name string
39+
input string
40+
want []string
41+
}{
42+
{
43+
name: "valid pattern - with keyword sql",
44+
input: fmt.Sprintf(`sql - %s`, validConnStr),
45+
want: []string{validParsedStr},
46+
},
47+
{
48+
name: "invalid pattern",
49+
input: fmt.Sprintf("sql=%s", invalidConnStr),
50+
want: []string{},
51+
},
52+
}
53+
54+
for _, test := range tests {
55+
t.Run(test.name, func(t *testing.T) {
56+
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
57+
if len(matchedDetectors) == 0 {
58+
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
59+
return
60+
}
61+
62+
results, err := d.FromData(context.Background(), false, []byte(test.input))
63+
if err != nil {
64+
t.Errorf("error = %v", err)
65+
return
66+
}
67+
68+
if len(results) != len(test.want) {
69+
if len(results) == 0 {
70+
t.Errorf("did not receive result")
71+
} else {
72+
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
73+
}
74+
return
75+
}
76+
77+
actual := make(map[string]struct{}, len(results))
78+
for _, r := range results {
79+
if len(r.RawV2) > 0 {
80+
actual[string(r.RawV2)] = struct{}{}
81+
} else {
82+
actual[string(r.Raw)] = struct{}{}
83+
}
84+
}
85+
expected := make(map[string]struct{}, len(test.want))
86+
for _, v := range test.want {
87+
expected[v] = struct{}{}
88+
}
89+
90+
if diff := cmp.Diff(expected, actual); diff != "" {
91+
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
92+
}
93+
})
94+
}
95+
}
96+
97+
func TestSqlServer_FromDataWithIgnorePattern(t *testing.T) {
98+
s := New(
99+
WithIgnorePattern([]string{
100+
`^Server=localhost`,
101+
}))
102+
got, err := s.FromData(context.Background(), false, []byte("Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true"))
103+
if err != nil {
104+
t.Errorf("FromData() error = %v", err)
105+
return
106+
}
107+
if len(got) != 0 {
108+
t.Errorf("expected no results, but got %d", len(got))
109+
}
110+
}

0 commit comments

Comments
 (0)