|
| 1 | +package v2 |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "io" |
| 6 | + |
| 7 | + // "log" |
| 8 | + "fmt" |
| 9 | + "net/http" |
| 10 | + |
| 11 | + regexp "github.com/wasilibs/go-re2" |
| 12 | + |
| 13 | + "github.com/trufflesecurity/trufflehog/v3/pkg/common" |
| 14 | + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" |
| 15 | + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" |
| 16 | +) |
| 17 | + |
| 18 | +type Scanner struct { |
| 19 | + client *http.Client |
| 20 | +} |
| 21 | + |
| 22 | +func (s Scanner) Version() int { return 2 } |
| 23 | + |
| 24 | +// Ensure the Scanner satisfies the interface at compile time. |
| 25 | +var _ interface { |
| 26 | + detectors.Detector |
| 27 | + detectors.Versioner |
| 28 | +} = (*Scanner)(nil) |
| 29 | + |
| 30 | +var ( |
| 31 | + defaultClient = common.SaneHttpClient() |
| 32 | + |
| 33 | + keyPat = regexp.MustCompile(`\b(pat-(?:eu|na)1-[A-Za-z0-9]{8}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{12})\b`) |
| 34 | +) |
| 35 | + |
| 36 | +// Keywords are used for efficiently pre-filtering chunks. |
| 37 | +// Use identifiers in the secret preferably, or the provider name. |
| 38 | +func (s Scanner) Keywords() []string { |
| 39 | + return []string{"pat-na1-", "pat-eu1-"} |
| 40 | +} |
| 41 | + |
| 42 | +// FromData will find and optionally verify HubSpotApiKey secrets in a given set of bytes. |
| 43 | +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { |
| 44 | + dataStr := string(data) |
| 45 | + |
| 46 | + uniqueMatches := make(map[string]struct{}) |
| 47 | + for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) { |
| 48 | + uniqueMatches[match[1]] = struct{}{} |
| 49 | + } |
| 50 | + |
| 51 | + for token := range uniqueMatches { |
| 52 | + s1 := detectors.Result{ |
| 53 | + DetectorType: detectorspb.DetectorType_HubSpotApiKey, |
| 54 | + Raw: []byte(token), |
| 55 | + Redacted: token[8:] + "...", |
| 56 | + } |
| 57 | + |
| 58 | + if verify { |
| 59 | + client := s.client |
| 60 | + if client == nil { |
| 61 | + client = defaultClient |
| 62 | + } |
| 63 | + |
| 64 | + verified, verificationErr := verifyToken(ctx, client, token) |
| 65 | + s1.Verified = verified |
| 66 | + s1.SetVerificationError(verificationErr) |
| 67 | + } |
| 68 | + |
| 69 | + results = append(results, s1) |
| 70 | + } |
| 71 | + |
| 72 | + return results, nil |
| 73 | +} |
| 74 | +func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) { |
| 75 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.hubapi.com/account-info/v3/api-usage/daily/private-apps", nil) |
| 76 | + if err != nil { |
| 77 | + return false, err |
| 78 | + } |
| 79 | + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) |
| 80 | + res, err := client.Do(req) |
| 81 | + if err != nil { |
| 82 | + return false, err |
| 83 | + } |
| 84 | + defer func() { |
| 85 | + _, _ = io.Copy(io.Discard, res.Body) |
| 86 | + _ = res.Body.Close() |
| 87 | + }() |
| 88 | + |
| 89 | + switch res.StatusCode { |
| 90 | + case http.StatusOK: |
| 91 | + return true, nil |
| 92 | + case http.StatusUnauthorized: |
| 93 | + return false, nil |
| 94 | + case http.StatusForbidden: |
| 95 | + // The token is valid but lacks permission for the endpoint. |
| 96 | + return true, nil |
| 97 | + default: |
| 98 | + return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +func (s Scanner) Type() detectorspb.DetectorType { |
| 103 | + return detectorspb.DetectorType_HubSpotApiKey |
| 104 | +} |
| 105 | + |
| 106 | +func (s Scanner) Description() string { |
| 107 | + return "HubSpot is a CRM platform that provides tools for marketing, sales, and customer service. HubSpot API keys can be used to access and modify data within the HubSpot platform." |
| 108 | +} |
0 commit comments