diff --git a/pkg/detectors/digitaloceanv2/digitaloceanv2.go b/pkg/detectors/digitaloceanv2/digitaloceanv2.go index 014d990031b0..daba6ad9ed41 100644 --- a/pkg/detectors/digitaloceanv2/digitaloceanv2.go +++ b/pkg/detectors/digitaloceanv2/digitaloceanv2.go @@ -3,6 +3,7 @@ package digitaloceanv2 import ( "context" "fmt" + "io" "net/http" "regexp" "strings" @@ -21,13 +22,13 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(`\b(dop\_v1\_[a-f0-9]{64})\b`) + keyPat = regexp.MustCompile(`\b((?:dop|doo|dor)_v1_[a-f0-9]{64})\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"dop_v1_"} + return []string{"dop_v1_", "doo_v1_", "dor_v1_"} } // FromData will find and optionally verify DigitalOceanV2 secrets in a given set of bytes. @@ -48,21 +49,52 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.digitalocean.com/v2/account", nil) - if err != nil { - continue - } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + switch { + case strings.HasPrefix(resMatch, "dor_v1_"): + req, err := http.NewRequestWithContext(ctx, "GET", "https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&refresh_token="+resMatch, nil) + if err != nil { + continue + } + + res, err := client.Do(req) + if err == nil { + bodyBytes, err := io.ReadAll(res.Body) + + if err != nil { continue } + + bodyString := string(bodyBytes) + validResponse := strings.Contains(bodyString, `"access_token"`) + defer res.Body.Close() + + if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse { + s1.Verified = true + } else { + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key + if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + } + } + + case strings.HasPrefix(resMatch, "doo_v1_"), strings.HasPrefix(resMatch, "dop_v1_"): + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.digitalocean.com/v2/account", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else { + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + } } } }