From d28724608b516782a142402dc4763fd8fe3b45e5 Mon Sep 17 00:00:00 2001 From: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:34:26 +0500 Subject: [PATCH] Refactored caflou detector (#4372) --- pkg/detectors/caflou/caflou.go | 55 +++++++++++++++++++---------- pkg/detectors/caflou/caflou_test.go | 43 ++++++++++------------ 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/pkg/detectors/caflou/caflou.go b/pkg/detectors/caflou/caflou.go index 3abbe3859..f0b8905c6 100644 --- a/pkg/detectors/caflou/caflou.go +++ b/pkg/detectors/caflou/caflou.go @@ -3,6 +3,7 @@ package caflou import ( "context" "fmt" + "io" "net/http" "strings" "time" @@ -25,7 +26,7 @@ var ( defaultClient = common.SaneHttpClientTimeOut(time.Second * 10) // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"caflou"}) + `\b([a-bA-Z0-9\S]{155})\b`) + keyPat = regexp.MustCompile(`\b(eyJhbGciOiJIUzI1NiJ9[a-zA-Z0-9._-]{135})\b`) ) // Keywords are used for efficiently pre-filtering chunks. @@ -34,6 +35,14 @@ func (s Scanner) Keywords() []string { return []string{"caflou"} } +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_Caflou +} + +func (s Scanner) Description() string { + return "Caflou is a business management software used for managing projects, tasks, and finances. Caflou API keys can be used to access and modify this data." +} + // FromData will find and optionally verify Caflou 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) @@ -54,18 +63,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result client = defaultClient } - req, err := http.NewRequestWithContext(ctx, "GET", "https://app.caflou.com/api/v1/accounts", 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, verificationErr := verifyCaflou(ctx, client, resMatch) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, resMatch) } results = append(results, s1) @@ -74,10 +74,29 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } -func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_Caflou -} +func verifyCaflou(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://app.caflou.com/api/v1/accounts", http.NoBody) + if err != nil { + return false, err + } -func (s Scanner) Description() string { - return "Caflou is a business management software used for managing projects, tasks, and finances. Caflou API keys can be used to access and modify this data." + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", 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/caflou/caflou_test.go b/pkg/detectors/caflou/caflou_test.go index 500a2fca0..aa5da0133 100644 --- a/pkg/detectors/caflou/caflou_test.go +++ b/pkg/detectors/caflou/caflou_test.go @@ -10,27 +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: - base_url: "https://api.example.com/instances" - api_key: $API_KEY - caflou_auth_token: "Bearer b8SQoPKLMCBbwIm0XDzbZiDydUk9qNqqBlKnR5Nouwbjs9cv3D1azAXpiFq9WrfNlwxbCwDL2FWCheXmdYKZkMRZklahJh5NQZZY7Zf220hjGJOtKgFbWxy9xQ9hodQqsOOx9Of30qtTrnRxFPa9wxYkSBn" - - # Notes: - # - Remember to rotate the secret every 90 days. - # - The above credentials should only be used in a secure environment. - ` - secret = "b8SQoPKLMCBbwIm0XDzbZiDydUk9qNqqBlKnR5Nouwbjs9cv3D1azAXpiFq9WrfNlwxbCwDL2FWCheXmdYKZkMRZklahJh5NQZZY7Zf220hjGJOtKgFbWxy9xQ9hodQqsOOx9Of30qtTrnRxFPa9wxYkSBn" -) - func TestCaflou_Pattern(t *testing.T) { d := Scanner{} ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) @@ -41,9 +20,25 @@ func TestCaflou_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: + base_url: "https://api.example.com/instances" + api_key: $API_KEY + caflou_auth_token: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI" + + # Notes: + # - Remember to rotate the secret every 90 days. + # - The above credentials should only be used in a secure environment. + `, + want: []string{"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI"}, }, }