diff --git a/pkg/detectors/currentsapi/currentsapi.go b/pkg/detectors/currentsapi/currentsapi.go index 25c40281d..63e3f8181 100644 --- a/pkg/detectors/currentsapi/currentsapi.go +++ b/pkg/detectors/currentsapi/currentsapi.go @@ -2,8 +2,9 @@ package currentsapi import ( "context" + "fmt" + "io" "net/http" - "strings" regexp "github.com/wasilibs/go-re2" @@ -21,9 +22,17 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currentsapi"}) + `\b([a-zA-Z0-9\S]{48})\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"currentsapi"}) + `([a-zA-Z0-9_-]{48})`) ) +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_CurrentsAPI +} + +func (s Scanner) Description() string { + return "CurrentsAPI provides access to the latest news and trends. CurrentsAPI keys can be used to authenticate requests and retrieve news data." +} + // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { @@ -34,29 +43,22 @@ func (s Scanner) Keywords() []string { func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) - matches := keyPat.FindAllStringSubmatch(dataStr, -1) + var uniqueTokens = make(map[string]struct{}) - for _, match := range matches { - resMatch := strings.TrimSpace(match[1]) + for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) { + uniqueTokens[match[1]] = struct{}{} + } + for token := range uniqueTokens { s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_CurrentsAPI, - Raw: []byte(resMatch), + Raw: []byte(token), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.currentsapi.services/v1/latest-news", nil) - if err != nil { - continue - } - req.Header.Add("Authorization", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, verificationErr := verifyCurrentsAPI(ctx, client, token) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, token) } results = append(results, s1) @@ -65,10 +67,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } -func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_CurrentsAPI -} +func verifyCurrentsAPI(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.currentsapi.services/v1/latest-news", http.NoBody) + if err != nil { + return false, err + } -func (s Scanner) Description() string { - return "CurrentsAPI provides access to the latest news and trends. CurrentsAPI keys can be used to authenticate requests and retrieve news data." + req.Header.Add("Authorization", token) + + resp, err := client.Do(req) + if err != nil { + return false, err + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + switch resp.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } } diff --git a/pkg/detectors/currentsapi/currentsapi_test.go b/pkg/detectors/currentsapi/currentsapi_test.go index 63351932b..505b33f04 100644 --- a/pkg/detectors/currentsapi/currentsapi_test.go +++ b/pkg/detectors/currentsapi/currentsapi_test.go @@ -10,28 +10,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" ) -var ( - validPattern = ` - # Configuration File: config.yaml - database: - host: $DB_HOST - port: $DB_PORT - username: $DB_USERNAME - password: $DB_PASS # IMPORTANT: Do not share this password publicly - - api: - auth_type: "" - in: "Header" - currentsapi_key: "thisistheapikey(asaspijf89aa$)lkunjo0#aenoiecnjh" - base_url: "https://api.example.com/v1/user" - - # Notes: - # - Remember to rotate the secret every 90 days. - # - The above credentials should only be used in a secure environment. - ` - secret = "thisistheapikey(asaspijf89aa$)lkunjo0#aenoiecnjh" -) - func TestCurrentsAPI_Pattern(t *testing.T) { d := Scanner{} ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) @@ -42,9 +20,40 @@ func TestCurrentsAPI_Pattern(t *testing.T) { want []string }{ { - name: "valid pattern", - input: validPattern, - want: []string{secret}, + name: "valid pattern", + input: ` + # Configuration File: config.yaml + database: + host: $DB_HOST + port: $DB_PORT + username: $DB_USERNAME + password: $DB_PASS # IMPORTANT: Do not share this password publicly + + api: + auth_type: "" + in: "Header" + currentsapi_key: "P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE" + base_url: "https://api.example.com/v1/user" + + # Notes: + # - Remember to rotate the secret every 90 days. + # - The above credentials should only be used in a secure environment. + `, + want: []string{"P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE"}, + }, + { + name: "valid pattern", + input: ` + + GLOBAL + {currentsapi} + {AQAAABAAA -WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG} + configuration for production + 2023-05-18T14:32:10Z + jenkins-admin + + `, + want: []string{"-WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG"}, }, }