Skip to content

Commit 32c3e96

Browse files
authored
fix(privacy): improve detector (#87)
1 parent 649f898 commit 32c3e96

2 files changed

Lines changed: 54 additions & 39 deletions

File tree

pkg/detectors/privacy/privacy.go

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package privacy
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"net/http"
7-
"strings"
88

99
regexp "github.com/wasilibs/go-re2"
1010

@@ -20,68 +20,83 @@ type Scanner struct {
2020
// Ensure the Scanner satisfies the interface at compile time.
2121
var _ detectors.Detector = (*Scanner)(nil)
2222

23+
func (s Scanner) Type() detectorspb.DetectorType {
24+
return detectorspb.DetectorType_Privacy
25+
}
26+
27+
func (s Scanner) Description() string {
28+
return "Privacy provides virtual cards for secure online payments. Privacy API keys can be used to manage and create these virtual cards."
29+
}
30+
2331
var (
24-
defaultClient = common.SaneHttpClient()
2532
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
2633
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"privacy"}) + `\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\b`)
2734
)
2835

2936
// Keywords are used for efficiently pre-filtering chunks.
3037
// Use identifiers in the secret preferably, or the provider name.
3138
func (s Scanner) Keywords() []string {
32-
return []string{"privacy"}
39+
return []string{"privacy.com"}
3340
}
3441

3542
// FromData will find and optionally verify Privacy secrets in a given set of bytes.
3643
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3744
dataStr := string(data)
3845

39-
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
40-
41-
for _, match := range matches {
42-
resMatch := strings.TrimSpace(match[1])
46+
uniqueMatches := make(map[string]struct{})
47+
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
48+
m := match[1]
49+
if detectors.StringShannonEntropy(m) < 3 {
50+
continue
51+
}
52+
uniqueMatches[m] = struct{}{}
53+
}
4354

44-
s1 := detectors.Result{
55+
for key := range uniqueMatches {
56+
r := detectors.Result{
4557
DetectorType: detectorspb.DetectorType_Privacy,
46-
Raw: []byte(resMatch),
58+
Raw: []byte(key),
4759
}
4860

4961
if verify {
50-
client := s.client
51-
if client == nil {
52-
client = defaultClient
53-
}
54-
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.privacy.com/v1/card?page=1&page_size=50", nil)
55-
if err != nil {
56-
continue
57-
}
58-
req.Header.Set("Authorization", "api-key "+resMatch)
59-
res, err := client.Do(req)
60-
if err == nil {
61-
defer res.Body.Close()
62-
if res.StatusCode >= 200 && res.StatusCode < 300 {
63-
s1.Verified = true
64-
} else if res.StatusCode == 401 {
65-
// The secret is determinately not verified (nothing to do)
66-
} else {
67-
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
68-
s1.SetVerificationError(err, resMatch)
69-
}
70-
} else {
71-
s1.SetVerificationError(err, resMatch)
62+
if s.client == nil {
63+
s.client = common.SaneHttpClient()
7264
}
65+
66+
isVerified, vErr := verifyMatch(ctx, s.client, key)
67+
r.Verified = isVerified
68+
r.SetVerificationError(vErr, key)
7369
}
7470

75-
results = append(results, s1)
71+
results = append(results, r)
7672
}
7773

7874
return results, nil
7975
}
8076

81-
func (s Scanner) Type() detectorspb.DetectorType {
82-
return detectorspb.DetectorType_Privacy
83-
}
77+
func verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) {
78+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.privacy.com/v1/card?page=1&page_size=50", nil)
79+
if err != nil {
80+
return false, err
81+
}
8482

85-
func (s Scanner) Description() string {
86-
return "Privacy provides virtual cards for secure online payments. Privacy API keys can be used to manage and create these virtual cards."
83+
req.Header.Set("Authorization", "api-key "+key)
84+
res, err := client.Do(req)
85+
if err != nil {
86+
return false, err
87+
}
88+
defer func() {
89+
_, _ = io.Copy(io.Discard, res.Body)
90+
_ = res.Body.Close()
91+
}()
92+
93+
switch res.StatusCode {
94+
case http.StatusOK:
95+
return true, nil
96+
case http.StatusUnauthorized:
97+
return false, nil
98+
default:
99+
body, _ := io.ReadAll(res.Body)
100+
return false, fmt.Errorf("unexpected HTTP response status %d, %q", res.StatusCode, string(body))
101+
}
87102
}

pkg/detectors/privacy/privacy_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
var (
1515
validPattern = "sccqe60k-nsak-h1m1-vzq8-vr7u4gxihiwl"
1616
invalidPattern = "sccqe60k?nsak-h1m1-vzq8-vr7u4gxihiwl"
17-
keyword = "privacy"
17+
keyword = "privacy.com"
1818
)
1919

2020
func TestPrivacy_Pattern(t *testing.T) {
@@ -34,7 +34,7 @@ func TestPrivacy_Pattern(t *testing.T) {
3434
name: "valid pattern - ignore duplicate",
3535
input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
3636
want: []string{validPattern},
37-
},
37+
},
3838
{
3939
name: "invalid pattern",
4040
input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),

0 commit comments

Comments
 (0)