From ee808727966824ff7ff66e414271d87820c398bc Mon Sep 17 00:00:00 2001 From: Nabeel Alam Date: Tue, 1 Jul 2025 15:07:44 +0500 Subject: [PATCH] [Update] Added Missing Indeterminate Verification Errors In Detectors Starting With Letter "A" (#4256) * added missing indeterminate verification errors in detectors starting from letter a * fixed atera, assemlyai detector indeterminate verification errors * added response body discard in asana detectors --- pkg/detectors/appcues/appcues.go | 43 +++++++--- pkg/detectors/appfollow/appfollow.go | 42 +++++++--- pkg/detectors/appointedd/appointedd.go | 54 ++++++++----- pkg/detectors/appsynergy/appsynergy.go | 54 ++++++++++--- pkg/detectors/apptivo/apptivo.go | 52 +++++++----- pkg/detectors/artsy/artsy.go | 40 +++++++--- pkg/detectors/asanaoauth/asanaoauth.go | 42 +++++++--- .../asanapersonalaccesstoken.go | 43 +++++++--- pkg/detectors/assemblyai/assemblyai.go | 46 +++++++---- pkg/detectors/atera/atera.go | 46 +++++++---- .../auth0managementapitoken.go | 48 +++++++---- pkg/detectors/autodesk/autodesk.go | 2 +- pkg/detectors/autoklose/autoklose.go | 76 ++++++++++-------- .../avazapersonalaccesstoken.go | 45 ++++++++--- pkg/detectors/aviationstack/aviationstack.go | 46 +++++++---- pkg/detectors/axonaut/axonaut.go | 45 ++++++++--- pkg/detectors/aylien/aylien.go | 46 +++++++---- pkg/detectors/ayrshare/ayrshare.go | 80 ++++++++++++------- pkg/detectors/azure_batch/azurebatch.go | 77 +++++++++++------- 19 files changed, 620 insertions(+), 307 deletions(-) diff --git a/pkg/detectors/appcues/appcues.go b/pkg/detectors/appcues/appcues.go index f03984c9a..5f378d95e 100644 --- a/pkg/detectors/appcues/appcues.go +++ b/pkg/detectors/appcues/appcues.go @@ -3,6 +3,7 @@ package appcues import ( "context" "fmt" + "io" "net/http" "strings" @@ -13,7 +14,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{ +type Scanner struct { detectors.DefaultMultiPartCredentialProvider } @@ -60,18 +61,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result RawV2: []byte(resMatch + resUserMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), nil) - if err != nil { - continue - } - req.SetBasicAuth(resUserMatch, resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resUserMatch, resMatch, resIdMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resUserMatch, resMatch, resIdMatch) } results = append(results, s1) @@ -82,6 +74,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, resUserMatch, resMatch, resIdMatch string) (bool, error) { + // Reference: https://api.appcues.com/v2/docs?_gl=1#responses + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), http.NoBody) + if err != nil { + return false, err + } + req.SetBasicAuth(resUserMatch, resMatch) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Appcues } diff --git a/pkg/detectors/appfollow/appfollow.go b/pkg/detectors/appfollow/appfollow.go index 5f2765526..c3e439adc 100644 --- a/pkg/detectors/appfollow/appfollow.go +++ b/pkg/detectors/appfollow/appfollow.go @@ -2,6 +2,8 @@ package appfollow import ( "context" + "fmt" + "io" "net/http" "strings" @@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appfollow.io/api/v2/account/users", nil) - if err != nil { - continue - } - req.Header.Add("X-AppFollow-API-Token", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -65,6 +58,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + // Reference: https://docs.api.appfollow.io/reference/users_list_api_v2_account_users_get-1 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appfollow.io/api/v2/account/users", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("X-AppFollow-API-Token", token) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK, http.StatusPaymentRequired, http.StatusUnprocessableEntity: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Appfollow } diff --git a/pkg/detectors/appointedd/appointedd.go b/pkg/detectors/appointedd/appointedd.go index 9c240ca6d..aed88100f 100644 --- a/pkg/detectors/appointedd/appointedd.go +++ b/pkg/detectors/appointedd/appointedd.go @@ -2,11 +2,13 @@ package appointedd import ( "context" - regexp "github.com/wasilibs/go-re2" + "fmt" "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -44,24 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result Raw: []byte(resMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appointedd.com/v1/availability/slots", nil) - if err != nil { - continue - } - req.Header.Add("X-API-KEY", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - continue - } - body := string(bodyBytes) - - if strings.Contains(body, "total") { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -70,6 +57,35 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.appointedd.com/v1/availability/slots", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("X-API-KEY", secret) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, err + } + return strings.Contains(string(bodyBytes), "total"), nil + case http.StatusUnauthorized, http.StatusForbidden: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Appointedd } diff --git a/pkg/detectors/appsynergy/appsynergy.go b/pkg/detectors/appsynergy/appsynergy.go index c5014de6a..79bcb67e8 100644 --- a/pkg/detectors/appsynergy/appsynergy.go +++ b/pkg/detectors/appsynergy/appsynergy.go @@ -3,10 +3,12 @@ package appsynergy import ( "context" "fmt" - regexp "github.com/wasilibs/go-re2" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -45,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - payload := strings.NewReader(`{"html":"

Hello World

","filename":"HelloWorld.pdf"}`) - req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://www.appsynergy.com/api?action=HTML2PDF&apiKey=%s", resMatch), payload) - 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 - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -65,6 +58,41 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) { + payload := strings.NewReader(`{"html":"

Hello World

","filename":"HelloWorld.pdf"}`) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://www.appsynergy.com/api?action=HTML2PDF&apiKey="+secret, payload) + if err != nil { + return false, err + } + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + case http.StatusBadRequest: + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, err + } + body := string(bodyBytes) + if strings.Contains(body, "Invalid API Key") { + return false, nil + } + return false, fmt.Errorf("status bad request invalid api key message not found: %d", res.StatusCode) + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AppSynergy } diff --git a/pkg/detectors/apptivo/apptivo.go b/pkg/detectors/apptivo/apptivo.go index bd68fc5c1..eb7882ecb 100644 --- a/pkg/detectors/apptivo/apptivo.go +++ b/pkg/detectors/apptivo/apptivo.go @@ -14,7 +14,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{ +type Scanner struct { detectors.DefaultMultiPartCredentialProvider } @@ -54,27 +54,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.apptivo.com/app/dao/v6/leads?a=getConfigData&apiKey=%s&accessKey=%s", resMatch, resIdMatch), 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, `displayName`) - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - if validResponse { - s1.Verified = true - } else { - s1.Verified = false - } - } - } + isVerified, err := verifyMatch(ctx, client, resMatch, resIdMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch, resIdMatch) } results = append(results, s1) @@ -84,6 +66,32 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, apiKey, accessKey string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://api.apptivo.com/app/dao/v6/leads?a=getConfigData&apiKey=%s&accessKey=%s", apiKey, accessKey), http.NoBody) + if err != nil { + return false, err + } + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, err + } + return strings.Contains(string(bodyBytes), `displayName`), nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Apptivo } diff --git a/pkg/detectors/artsy/artsy.go b/pkg/detectors/artsy/artsy.go index 6da207de1..89b6f8eaf 100644 --- a/pkg/detectors/artsy/artsy.go +++ b/pkg/detectors/artsy/artsy.go @@ -2,6 +2,8 @@ package artsy import ( "context" + "fmt" + "io" "net/http" "strings" @@ -54,17 +56,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "POST", "https://api.artsy.net/api/tokens/xapp_token?client_id="+resIdMatch+"&client_secret="+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 - } - } + isVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -75,6 +69,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) { + // Reference: https://developers.artsy.net/v2/docs/authentication + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.artsy.net/api/tokens/xapp_token?client_id="+id+"&client_secret="+secret, http.NoBody) + if err != nil { + return false, err + } + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusCreated: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Artsy } diff --git a/pkg/detectors/asanaoauth/asanaoauth.go b/pkg/detectors/asanaoauth/asanaoauth.go index 85bcfa31f..83de2aef1 100644 --- a/pkg/detectors/asanaoauth/asanaoauth.go +++ b/pkg/detectors/asanaoauth/asanaoauth.go @@ -3,6 +3,7 @@ package asanaoauth import ( "context" "fmt" + "io" "net/http" "strings" @@ -46,19 +47,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://app.asana.com/api/1.0/users/me", 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 - s1.AnalysisInfo = map[string]string{"key": resMatch} - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) + s1.AnalysisInfo = map[string]string{"key": resMatch} } results = append(results, s1) @@ -67,6 +59,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.asana.com/api/1.0/users/me", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AsanaOauth } diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go index 050ea1b6f..abd1ed75e 100644 --- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go +++ b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go @@ -3,10 +3,12 @@ package asanapersonalaccesstoken import ( "context" "fmt" - regexp "github.com/wasilibs/go-re2" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -44,18 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://app.asana.com/api/1.0/users/me", 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 - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -64,6 +57,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.asana.com/api/1.0/users/me", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AsanaPersonalAccessToken } diff --git a/pkg/detectors/assemblyai/assemblyai.go b/pkg/detectors/assemblyai/assemblyai.go index 47ef5db0d..73441ccbb 100644 --- a/pkg/detectors/assemblyai/assemblyai.go +++ b/pkg/detectors/assemblyai/assemblyai.go @@ -2,10 +2,13 @@ package assemblyai import ( "context" - regexp "github.com/wasilibs/go-re2" + "fmt" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -44,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.assemblyai.com/v2/transcript", nil) - if err != nil { - continue - } - req.Header.Add("Content-Type", "application/json") - 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, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -65,6 +58,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.assemblyai.com/v2/transcript", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Authorization", token) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AssemblyAI } diff --git a/pkg/detectors/atera/atera.go b/pkg/detectors/atera/atera.go index cc7eba7a4..d18650490 100644 --- a/pkg/detectors/atera/atera.go +++ b/pkg/detectors/atera/atera.go @@ -2,10 +2,13 @@ package atera import ( "context" - regexp "github.com/wasilibs/go-re2" + "fmt" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -44,19 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://app.atera.com/api/v3/alerts", nil) - if err != nil { - continue - } - req.Header.Add("Accept", "application/json") - req.Header.Add("X-API-KEY", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -65,6 +58,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.atera.com/api/v3/alerts", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("X-API-KEY", token) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Atera } diff --git a/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go b/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go index d7058f384..bffa672e9 100644 --- a/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go +++ b/pkg/detectors/auth0managementapitoken/auth0managementapitoken.go @@ -3,6 +3,7 @@ package auth0managementapitoken import ( "context" "fmt" + "io" "net/http" "strings" @@ -61,22 +62,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - /* - curl -H "Authorization: Bearer $token" https://domain/api/v2/users - */ - - req, err := http.NewRequestWithContext(ctx, "GET", "https://"+domainRes+"/api/v2/users", nil) - if err != nil { - continue - } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", managementAPITokenRes)) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, managementAPITokenRes, domainRes) + s1.Verified = isVerified + s1.SetVerificationError(err, managementAPITokenRes) } results = append(results, s1) @@ -86,6 +74,34 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token, domain string) (bool, error) { + /* + curl -H "Authorization: Bearer $token" https://domain/api/v2/users + Reference: https://auth0.com/docs/api/management/v2/users/get-users + */ + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+domain+"/api/v2/users", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + switch res.StatusCode { + case http.StatusOK, http.StatusForbidden: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Auth0ManagementApiToken } diff --git a/pkg/detectors/autodesk/autodesk.go b/pkg/detectors/autodesk/autodesk.go index e30f6ae99..43f391abf 100644 --- a/pkg/detectors/autodesk/autodesk.go +++ b/pkg/detectors/autodesk/autodesk.go @@ -13,7 +13,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{ +type Scanner struct { detectors.DefaultMultiPartCredentialProvider } diff --git a/pkg/detectors/autoklose/autoklose.go b/pkg/detectors/autoklose/autoklose.go index a34982cc6..e675257af 100644 --- a/pkg/detectors/autoklose/autoklose.go +++ b/pkg/detectors/autoklose/autoklose.go @@ -48,38 +48,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - // API Documentation: https://api.aklab.xyz/#auth-info-fd71acd1-2e41-4991-8789-3edfd258479a - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.autoklose.com/api/me/?api_token=%s", resMatch), nil) - if err != nil { - continue - } - req.Header.Add("Accept", "application/json") - res, err := client.Do(req) - if err == nil { - defer func() { - _, _ = io.Copy(io.Discard, res.Body) - _ = res.Body.Close() - }() - - if res.StatusCode == http.StatusOK { - s1.Verified = true - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - continue - } - - var responseBody map[string]interface{} - if err := json.Unmarshal(bodyBytes, &responseBody); err == nil { - if email, ok := responseBody["email"].(string); ok { - s1.ExtraData = map[string]string{ - "email": email, - } - } - } - } - } else { - s1.SetVerificationError(err, resMatch) - } + isVerified, extraData, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.ExtraData = extraData + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -88,6 +60,46 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) { + // API Documentation: https://api.aklab.xyz/#auth-info-fd71acd1-2e41-4991-8789-3edfd258479a + url := fmt.Sprintf("https://api.autoklose.com/api/me/?api_token=%s", token) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) + if err != nil { + return false, nil, err + } + req.Header.Add("Accept", "application/json") + res, err := client.Do(req) + if err != nil { + return false, nil, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, nil, err + } + + var responseBody map[string]interface{} + if err := json.Unmarshal(bodyBytes, &responseBody); err != nil { + return false, nil, err + } + + if email, ok := responseBody["email"].(string); ok { + return true, map[string]string{"email": email}, nil + } + return true, nil, nil + case http.StatusUnauthorized: + return false, nil, nil + default: + return false, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Autoklose } diff --git a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go b/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go index 942742190..d9fcbadf8 100644 --- a/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go +++ b/pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go @@ -3,10 +3,12 @@ package avazapersonalaccesstoken import ( "context" "fmt" - regexp "github.com/wasilibs/go-re2" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -46,18 +48,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.avaza.com/api/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 - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -66,6 +59,32 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + // API Documentation: https://api.avaza.com/swagger/ui/index#!/Account/Account_Get + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.avaza.com/api/Account", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AvazaPersonalAccessToken } diff --git a/pkg/detectors/aviationstack/aviationstack.go b/pkg/detectors/aviationstack/aviationstack.go index 805ac3c77..8626e9ac1 100644 --- a/pkg/detectors/aviationstack/aviationstack.go +++ b/pkg/detectors/aviationstack/aviationstack.go @@ -3,11 +3,13 @@ package aviationstack import ( "context" "fmt" - regexp "github.com/wasilibs/go-re2" + "io" "net/http" "strings" "time" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -46,19 +48,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - timeout := 10 * time.Second - client.Timeout = timeout - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.aviationstack.com/v1/flights?access_key=%s", 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 - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -67,6 +59,32 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) { + client.Timeout = 10 * time.Second + url := fmt.Sprintf("https://api.aviationstack.com/v1/flights?access_key=%s", token) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) + if err != nil { + return false, err + } + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_AviationStack } diff --git a/pkg/detectors/axonaut/axonaut.go b/pkg/detectors/axonaut/axonaut.go index 194c0329d..04a4549f4 100644 --- a/pkg/detectors/axonaut/axonaut.go +++ b/pkg/detectors/axonaut/axonaut.go @@ -2,10 +2,13 @@ package axonaut import ( "context" - regexp "github.com/wasilibs/go-re2" + "fmt" + "io" "net/http" "strings" + regexp "github.com/wasilibs/go-re2" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -44,18 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://axonaut.com/api/v2/companies?type=all&sort=id", nil) - if err != nil { - continue - } - req.Header.Add("userApiKey", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -64,6 +58,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://axonaut.com/api/v2/companies?type=all&sort=id", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("userApiKey", key) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusForbidden: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Axonaut } diff --git a/pkg/detectors/aylien/aylien.go b/pkg/detectors/aylien/aylien.go index f40c7424f..1006b7f59 100644 --- a/pkg/detectors/aylien/aylien.go +++ b/pkg/detectors/aylien/aylien.go @@ -2,6 +2,8 @@ package aylien import ( "context" + "fmt" + "io" "net/http" "strings" @@ -12,7 +14,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{ +type Scanner struct { detectors.DefaultMultiPartCredentialProvider } @@ -52,19 +54,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result RawV2: []byte(resMatch + resIdMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.aylien.com/news/stories", nil) - if err != nil { - continue - } - req.Header.Add("X-AYLIEN-NewsAPI-Application-ID", resIdMatch) - req.Header.Add("X-AYLIEN-NewsAPI-Application-Key", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } - } + isVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -74,6 +66,32 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, id, key string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.aylien.com/news/stories", http.NoBody) + if err != nil { + return false, err + } + req.Header.Add("X-AYLIEN-NewsAPI-Application-ID", id) + req.Header.Add("X-AYLIEN-NewsAPI-Application-Key", key) + res, err := client.Do(req) + if err != nil { + return false, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusUnauthorized: + return false, nil + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Aylien } diff --git a/pkg/detectors/ayrshare/ayrshare.go b/pkg/detectors/ayrshare/ayrshare.go index df60114f5..6ac4e3ae0 100644 --- a/pkg/detectors/ayrshare/ayrshare.go +++ b/pkg/detectors/ayrshare/ayrshare.go @@ -48,37 +48,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://app.ayrshare.com/api/user", nil) - if err != nil { - continue - } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) - res, err := client.Do(req) - if err == nil { - defer func() { - _, _ = io.Copy(io.Discard, res.Body) - _ = res.Body.Close() - }() - - if res.StatusCode == http.StatusOK { - s1.Verified = true - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - continue - } - - var responseBody map[string]interface{} - if err := json.Unmarshal(bodyBytes, &responseBody); err == nil { - if email, ok := responseBody["email"].(string); ok { - s1.ExtraData = map[string]string{ - "email": email, - } - } - } - } - } else { - s1.SetVerificationError(err, resMatch) - } + isVerified, extraData, err := verifyMatch(ctx, client, resMatch) + s1.Verified = isVerified + s1.ExtraData = extraData + s1.SetVerificationError(err, resMatch) } results = append(results, s1) @@ -87,6 +60,51 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) { + // Reference: https://www.ayrshare.com/docs/apis/user/profile-details + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.ayrshare.com/api/user", http.NoBody) + if err != nil { + return false, nil, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key)) + res, err := client.Do(req) + if err != nil { + return false, nil, err + } + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, nil, err + } + var responseBody map[string]any + if err := json.Unmarshal(bodyBytes, &responseBody); err == nil { + if email, ok := responseBody["email"].(string); ok { + return true, map[string]string{"email": email}, nil + } + } + return true, nil, nil + case http.StatusUnauthorized: + return false, nil, nil + case http.StatusForbidden: + // Invalid Bearer tokens get a 403 Forbidden response despite what is stated in the docs. + // Documentation: https://www.ayrshare.com/docs/errors/errors-http + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, nil, err + } + if strings.Contains(string(bodyBytes), "API Key not valid") { + return false, nil, nil + } + } + return false, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_Ayrshare } diff --git a/pkg/detectors/azure_batch/azurebatch.go b/pkg/detectors/azure_batch/azurebatch.go index 8165dfb44..ad2110da2 100644 --- a/pkg/detectors/azure_batch/azurebatch.go +++ b/pkg/detectors/azure_batch/azurebatch.go @@ -65,35 +65,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result if client == nil { client = defaultClient } - url := fmt.Sprintf("%s/applications?api-version=2020-09-01.12.0", endpoint) - date := time.Now().UTC().Format(http.TimeFormat) - stringToSign := fmt.Sprintf( - "GET\n\n\n\n\napplication/json\n%s\n\n\n\n\n\n%s\napi-version:%s", - date, - strings.ToLower(fmt.Sprintf("/%s/applications", accountName)), - "2020-09-01.12.0", - ) - key, _ := base64.StdEncoding.DecodeString(accountKey) - h := hmac.New(sha256.New, key) - h.Write([]byte(stringToSign)) - signature := base64.StdEncoding.EncodeToString(h.Sum(nil)) - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - continue - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("SharedKey %s:%s", accountName, signature)) - req.Header.Set("Date", date) - resp, err := client.Do(req) - if err != nil { - continue - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - s1.Verified = true - } - + isVerified, err := verifyMatch(ctx, client, endpoint, accountName, accountKey) + s1.Verified = isVerified + s1.SetVerificationError(err) } results = append(results, s1) @@ -106,6 +80,51 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func verifyMatch(ctx context.Context, client *http.Client, endpoint, accountName, accountKey string) (bool, error) { + // Reference: https://learn.microsoft.com/en-us/rest/api/batchservice/application/list + url := fmt.Sprintf("%s/applications?api-version=2020-09-01.12.0", endpoint) + + date := time.Now().UTC().Format(http.TimeFormat) + stringToSign := fmt.Sprintf( + "GET\n\n\n\n\napplication/json\n%s\n\n\n\n\n\n%s\napi-version:%s", + date, + strings.ToLower(fmt.Sprintf("/%s/applications", accountName)), + "2020-09-01.12.0", + ) + key, _ := base64.StdEncoding.DecodeString(accountKey) + h := hmac.New(sha256.New, key) + h.Write([]byte(stringToSign)) + signature := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) + if err != nil { + return false, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("SharedKey %s:%s", accountName, signature)) + req.Header.Set("Date", date) + resp, err := client.Do(req) + if err != nil { + // If the host is not found, we can assume that the endpoint is invalid + if strings.Contains(err.Error(), "no such host") { + return false, nil + } + return false, err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusForbidden: + // Key is either invalid or the account is disabled. + return false, nil + default: + return false, fmt.Errorf("unexpected status code %d for %s", resp.StatusCode, url) + } +} + func (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) { return false, "" }