Skip to content

Commit 0db029b

Browse files
Merge branch 'main' into INS-9-capture-organization-id-in-atlassian-analyzer-to-pass-into-analysisinfo
2 parents 02d946b + 84d5a49 commit 0db029b

File tree

1 file changed

+67
-31
lines changed

1 file changed

+67
-31
lines changed

pkg/detectors/sonarcloud/sonarcloud.go

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,27 @@ package sonarcloud
22

33
import (
44
"context"
5-
regexp "github.com/wasilibs/go-re2"
5+
"encoding/json"
6+
"fmt"
67
"io"
78
"net/http"
8-
"strings"
9+
10+
regexp "github.com/wasilibs/go-re2"
911

1012
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
1113
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
1214
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
1315
)
1416

15-
type Scanner struct{}
17+
type Scanner struct {
18+
client *http.Client
19+
}
1620

1721
// Ensure the Scanner satisfies the interface at compile time.
1822
var _ detectors.Detector = (*Scanner)(nil)
1923

2024
var (
21-
client = common.SaneHttpClient()
25+
defaultClient = common.SaneHttpClient()
2226

2327
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
2428
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sonar"}) + `(?:^|[^@])\b([0-9a-z]{40})\b`)
@@ -30,43 +34,33 @@ func (s Scanner) Keywords() []string {
3034
return []string{"sonar"}
3135
}
3236

37+
func (s Scanner) getClient() *http.Client {
38+
if s.client != nil {
39+
return s.client
40+
}
41+
42+
return defaultClient
43+
}
44+
3345
// FromData will find and optionally verify SonarCloud secrets in a given set of bytes.
3446
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3547
dataStr := string(data)
3648

37-
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
38-
39-
for _, match := range matches {
40-
resMatch := strings.TrimSpace(match[1])
49+
uniqueTokenMatches := make(map[string]struct{})
50+
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
51+
uniqueTokenMatches[match[1]] = struct{}{}
52+
}
4153

54+
for match := range uniqueTokenMatches {
4255
s1 := detectors.Result{
4356
DetectorType: detectorspb.DetectorType_SonarCloud,
44-
Raw: []byte(resMatch),
57+
Raw: []byte(match),
4558
}
4659

4760
if verify {
48-
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resMatch+"@sonarcloud.io/api/authentication/validate", nil)
49-
if err != nil {
50-
continue
51-
}
52-
res, err := client.Do(req)
53-
if err == nil {
54-
bodyBytes, err := io.ReadAll(res.Body)
55-
if err != nil {
56-
continue
57-
}
58-
59-
bodyString := string(bodyBytes)
60-
validResponse := strings.Contains(bodyString, `"valid":true`)
61-
62-
defer res.Body.Close()
63-
if res.StatusCode >= 200 && res.StatusCode < 300 {
64-
if validResponse {
65-
s1.Verified = true
66-
}
67-
}
68-
}
69-
61+
isVerified, verificationErr := s.verifyMatch(ctx, s.getClient(), match)
62+
s1.Verified = isVerified
63+
s1.SetVerificationError(verificationErr, match)
7064
}
7165

7266
results = append(results, s1)
@@ -75,6 +69,48 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
7569
return results, nil
7670
}
7771

72+
// verifyMatch attempts to validate a SonarCloud token.
73+
func (s Scanner) verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {
74+
url := "https://sonarcloud.io/api/authentication/validate"
75+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
76+
if err != nil {
77+
return false, fmt.Errorf("failed to create request: %w", err)
78+
}
79+
80+
req.SetBasicAuth(token, "")
81+
res, err := client.Do(req)
82+
if err != nil {
83+
return false, fmt.Errorf("failed to perform request: %w", err)
84+
}
85+
86+
defer func() {
87+
_, _ = io.Copy(io.Discard, res.Body)
88+
_ = res.Body.Close()
89+
}()
90+
91+
// The SonarCloud API always returns 200 OK, even for invalid tokens,
92+
// with the validity indicated in the JSON body.
93+
if res.StatusCode != http.StatusOK {
94+
// Treat any non-200 status as a failed attempt to verify.
95+
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
96+
}
97+
98+
bodyBytes, err := io.ReadAll(res.Body)
99+
if err != nil {
100+
return false, fmt.Errorf("failed to read response body: %w", err)
101+
}
102+
103+
var resp struct {
104+
Valid bool `json:"valid"`
105+
}
106+
107+
if err := json.Unmarshal(bodyBytes, &resp); err != nil {
108+
return false, fmt.Errorf("invalid JSON: %w", err)
109+
}
110+
111+
return resp.Valid, nil
112+
}
113+
78114
func (s Scanner) Type() detectorspb.DetectorType {
79115
return detectorspb.DetectorType_SonarCloud
80116
}

0 commit comments

Comments
 (0)