diff --git a/pkg/detectors/apiflash/apiflash.go b/pkg/detectors/apiflash/apiflash.go index 85bdba85f720..d4ef185d213a 100644 --- a/pkg/detectors/apiflash/apiflash.go +++ b/pkg/detectors/apiflash/apiflash.go @@ -3,6 +3,7 @@ package apiflash import ( "context" "fmt" + "io" "net/http" "strings" @@ -24,7 +25,8 @@ var ( // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apiflash"}) + `\b([a-z0-9]{32})\b`) - urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apiflash"}) + `\b([a-zA-Z0-9\S]{21,30})\b`) + + urlToCapture = "http://google.com" // a fix constant url to capture to verify the access key ) // Keywords are used for efficiently pre-filtering chunks. @@ -36,43 +38,25 @@ func (s Scanner) Keywords() []string { // FromData will find and optionally verify Apiflash secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) - matches := keyPat.FindAllStringSubmatch(dataStr, -1) - urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1) - for _, match := range matches { - if len(match) != 2 { - continue + uniqueAPIKeys := make(map[string]struct{}) + for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) { + uniqueAPIKeys[strings.TrimSpace(match[1])] = struct{}{} + } + + for key := range uniqueAPIKeys { + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_Apiflash, + Raw: []byte(key), } - resMatch := strings.TrimSpace(match[1]) - - for _, urlMatch := range urlMatches { - if len(urlMatch) != 2 { - continue - } - - resUrlMatch := strings.TrimSpace(urlMatch[1]) - s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_Apiflash, - Raw: []byte(resMatch), - RawV2: []byte(resMatch + resUrlMatch), - } - - if verify { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.apiflash.com/v1/urltoimage?url=%s&access_key=%s", resUrlMatch, resMatch), nil) - if err != nil { - continue - } - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } - } - - results = append(results, s1) + + if verify { + isVerified, verificationErr := verifyAPIFlash(ctx, client, key) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, key) } + + results = append(results, s1) } return results, nil @@ -85,3 +69,28 @@ func (s Scanner) Type() detectorspb.DetectorType { func (s Scanner) Description() string { return "Apiflash is a screenshot API service. Apiflash keys can be used to access and utilize the screenshot API service." } + +func verifyAPIFlash(ctx context.Context, client *http.Client, accessKey string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.apiflash.com/v1/urltoimage?url=%s&access_key=%s&wait_until=page_loaded", urlToCapture, accessKey), http.NoBody) + if err != nil { + return false, err + } + + resp, err := client.Do(req) + if err != nil { + return false, nil + } + 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/apiflash/apiflash_test.go b/pkg/detectors/apiflash/apiflash_test.go index cae1a3b72024..87e8b3b0cf1d 100644 --- a/pkg/detectors/apiflash/apiflash_test.go +++ b/pkg/detectors/apiflash/apiflash_test.go @@ -14,7 +14,6 @@ import ( var ( validPattern = ` apiflash_key: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 - apiflash_url: abc123XYZ456def789ghijklm ` invalidPattern = "0123456789Gbcde^0123456789abcdef" ) @@ -31,7 +30,7 @@ func TestApiFlash_Pattern(t *testing.T) { { name: "valid pattern", input: fmt.Sprintf("apiflash credentials: %s", validPattern), - want: []string{"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6abc123XYZ456def789ghijklm"}, + want: []string{"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"}, }, { name: "invalid pattern",