Skip to content

Commit 13a5e05

Browse files
authored
Merge branch 'main' into jwt
2 parents 8899a54 + 682eda6 commit 13a5e05

File tree

3 files changed

+89
-47
lines changed

3 files changed

+89
-47
lines changed

README.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ This requires Cosign binary to be installed prior to running the installation sc
185185
Command:
186186

187187
```bash
188-
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified,unknown
188+
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified
189189
```
190190

191191
Expected output:
@@ -209,15 +209,15 @@ Timestamp: 2022-06-16 10:17:40 -0700 PDT
209209
## 2: Scan a GitHub Org for only verified secrets
210210

211211
```bash
212-
trufflehog github --org=trufflesecurity --results=verified,unknown
212+
trufflehog github --org=trufflesecurity --results=verified
213213
```
214214

215-
## 3: Scan a GitHub Repo for only verified keys and get JSON output
215+
## 3: Scan a GitHub Repo for only verified secrets and get JSON output
216216

217217
Command:
218218

219219
```bash
220-
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified,unknown --json
220+
trufflehog git https://github.com/trufflesecurity/test_keys --results=verified --json
221221
```
222222

223223
Expected output:
@@ -233,7 +233,7 @@ Expected output:
233233
trufflehog github --repo=https://github.com/trufflesecurity/test_keys --issue-comments --pr-comments
234234
```
235235

236-
## 5: Scan an S3 bucket for verified keys
236+
## 5: Scan an S3 bucket for high-confidence results (verified + unknown)
237237

238238
```bash
239239
trufflehog s3 --bucket=<bucket name> --results=verified,unknown
@@ -269,25 +269,25 @@ Run trufflehog from the parent directory (outside the git repo).
269269
$ trufflehog git file://test_keys --results=verified,unknown
270270
```
271271

272-
## 10: Scan GCS buckets for verified secrets
272+
## 10: Scan GCS buckets for only verified secrets
273273

274274
```bash
275-
trufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified,unknown
275+
trufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified
276276
```
277277

278-
## 11: Scan a Docker image for verified secrets
278+
## 11: Scan a Docker image for only verified secrets
279279

280280
Use the `--image` flag multiple times to scan multiple images.
281281

282282
```bash
283283
# to scan from a remote registry
284-
trufflehog docker --image trufflesecurity/secrets --results=verified,unknown
284+
trufflehog docker --image trufflesecurity/secrets --results=verified
285285

286286
# to scan from the local docker daemon
287-
trufflehog docker --image docker://new_image:tag --results=verified,unknown
287+
trufflehog docker --image docker://new_image:tag --results=verified
288288

289289
# to scan from an image saved as a tarball
290-
trufflehog docker --image file://path_to_image.tar --results=verified,unknown
290+
trufflehog docker --image file://path_to_image.tar --results=verified
291291
```
292292

293293
## 12: Scan in CI
@@ -389,7 +389,7 @@ aws s3 cp s3://example/gzipped/data.gz - | gunzip -c | trufflehog stdin
389389
- Why is the scan taking a long time when I scan a GitHub org
390390
- Unauthenticated GitHub scans have rate limits. To improve your rate limits, include the `--token` flag with a personal access token
391391
- It says a private key was verified, what does that mean?
392-
- Check out our Driftwood blog post to learn how to do this, in short we've confirmed the key can be used live for SSH or SSL [Blog post](https://trufflesecurity.com/blog/driftwood-know-if-private-keys-are-sensitive/)
392+
- A verified result means TruffleHog confirmed the credential is valid by testing it against the service's API. For private keys, we've confirmed the key can be used live for SSH or SSL authentication. Check out our Driftwood blog post to learn more [Blog post](https://trufflesecurity.com/blog/driftwood-know-if-private-keys-are-sensitive/)
393393
- Is there an easy way to ignore specific secrets?
394394
- If the scanned source [supports line numbers](https://github.com/trufflesecurity/trufflehog/blob/d6375ba92172fd830abb4247cca15e3176448c5d/pkg/engine/engine.go#L358-L365), then you can add a `trufflehog:ignore` comment on the line containing the secret to ignore that secrets.
395395

@@ -405,7 +405,13 @@ TruffleHog v3 is a complete rewrite in Go with many new powerful features.
405405

406406
## What is credential verification?
407407

408-
For every potential credential that is detected, we've painstakingly implemented programmatic verification against the API that we think it belongs to. Verification eliminates false positives. For example, the [AWS credential detector](pkg/detectors/aws/aws.go) performs a `GetCallerIdentity` API call against the AWS API to verify if an AWS credential is active.
408+
For every potential credential that is detected, we've painstakingly implemented programmatic verification against the API that we think it belongs to. Verification eliminates false positives and provides three result statuses:
409+
410+
- **verified**: Credential confirmed as valid and active by API testing
411+
- **unverified**: Credential detected but not confirmed valid (may be invalid, expired, or verification disabled)
412+
- **unknown**: Verification attempted but failed due to errors, such as a network or API failure
413+
414+
For example, the [AWS credential detector](pkg/detectors/aws/aws.go) performs a `GetCallerIdentity` API call against the AWS API to verify if an AWS credential is active.
409415

410416
# :memo: Usage
411417

@@ -444,7 +450,7 @@ Flags:
444450
--github-actions Output in GitHub Actions format.
445451
--concurrency=20 Number of concurrent workers.
446452
--no-verification Don't verify the results.
447-
--results=RESULTS Specifies which type(s) of results to output: verified, unknown, unverified, filtered_unverified. Defaults to all types.
453+
--results=RESULTS Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to all types.
448454
--allow-verification-overlap
449455
Allow verification of similar credentials across detectors
450456
--filter-unverified Only output first unverified result per chunk per detector if there are more than one results.
@@ -677,7 +683,7 @@ webhook is used containing the regular expression matches.
677683
678684
TruffleHog will send a JSON POST request containing the regex matches to a
679685
configured webhook endpoint. If the endpoint responds with a `200 OK` response
680-
status code, the secret is considered verified.
686+
status code, the secret is considered verified. If verification fails due to network/API errors, the result is marked as unknown.
681687

682688
Custom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,
683689
and excluded word lists checked against the secret (captured group if present, entire match if capture group is not present). Note that if

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var (
5858
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
5959
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
6060
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Hidden().Bool()
61-
results = cli.Flag("results", "Specifies which type(s) of results to output: verified, unknown, unverified, filtered_unverified. Defaults to verified,unverified,unknown.").String()
61+
results = cli.Flag("results", "Specifies which type(s) of results to output: verified (confirmed valid by API), unknown (verification failed due to error), unverified (detected but not verified), filtered_unverified (unverified but would have been filtered out). Defaults to verified,unverified,unknown.").String()
6262
noColor = cli.Flag("no-color", "Disable colorized output").Bool()
6363
noColour = cli.Flag("no-colour", "Alias for --no-color").Hidden().Bool()
6464

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)