mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-10 07:04:24 +00:00
feat(hubspot): unify v1 and v2 implementation
This commit is contained in:
parent
a4ca1c749c
commit
30ace6e5e4
8 changed files with 294 additions and 88 deletions
|
@ -1,4 +1,4 @@
|
|||
package hubspotapikey
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -17,11 +17,13 @@ type Scanner struct {
|
|||
client *http.Client
|
||||
}
|
||||
|
||||
func (s Scanner) Version() int { return 1 }
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
var _ detectors.Versioner = (*Scanner)(nil)
|
||||
var _ interface {
|
||||
detectors.Detector
|
||||
detectors.Versioner
|
||||
} = (*Scanner)(nil)
|
||||
|
||||
func (s Scanner) Version() int { return 1 }
|
||||
|
||||
var (
|
||||
defaultClient = common.SaneHttpClient()
|
||||
|
@ -32,7 +34,7 @@ var (
|
|||
// 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{"hubspot", "hubapi", "hapikey", "hapi_key"}
|
||||
return []string{"hubapi", "hapikey", "hapi_key", "hubspot"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify HubSpotApiKey secrets in a given set of bytes.
|
|
@ -1,7 +1,7 @@
|
|||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package hubspotapikey
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
91
pkg/detectors/hubspot_apikey/v1/hubspot_apikey_v1_test.go
Normal file
91
pkg/detectors/hubspot_apikey/v1/hubspot_apikey_v1_test.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
|
||||
)
|
||||
|
||||
func TestHubspotV1_Pattern(t *testing.T) {
|
||||
d := Scanner{}
|
||||
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "hapikey",
|
||||
input: `// const hapikey = 'b714cac4-a45c-42af-9905-da4de8838d75';
|
||||
const { HAPI_KEY } = process.env;
|
||||
const hs = new HubSpotAPI({ hapikey: HAPI_KEY });`,
|
||||
want: []string{"b714cac4-a45c-42af-9905-da4de8838d75"},
|
||||
},
|
||||
// TODO: Doesn't work because it's more than 40 characters.
|
||||
// {
|
||||
// name: "hubapi",
|
||||
// input: `curl https://api.hubapi.com/contacts/v1/lists/all/contacts/all \
|
||||
//--header "Authorization: Bearer b71aa2ed-9c76-417d-bd8e-c5f4980d21ef"`,
|
||||
// want: []string{"b71aa2ed-9c76-417d-bd8e-c5f4980d21ef"},
|
||||
// },
|
||||
{
|
||||
name: "hubspot_1",
|
||||
input: `const hs = new HubSpotAPI("76a836c8-469d-4426-8a3b-194ca930b7a1");
|
||||
|
||||
const blogPosts = hs.blog.getPosts({ name: 'Inbound' });`,
|
||||
want: []string{"76a836c8-469d-4426-8a3b-194ca930b7a1"},
|
||||
},
|
||||
{
|
||||
name: "hubspot_2",
|
||||
input: ` 'hubspot' => [
|
||||
// 'api_key' => 'e9ff285d-6b7f-455a-a56d-9ec8c4abbd47', // @ts dev`,
|
||||
want: []string{"e9ff285d-6b7f-455a-a56d-9ec8c4abbd47"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
|
||||
if len(matchedDetectors) == 0 {
|
||||
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := d.FromData(context.Background(), false, []byte(test.input))
|
||||
if err != nil {
|
||||
t.Errorf("error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(results) != len(test.want) {
|
||||
if len(results) == 0 {
|
||||
t.Errorf("did not receive result")
|
||||
} else {
|
||||
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
actual := make(map[string]struct{}, len(results))
|
||||
for _, r := range results {
|
||||
if len(r.RawV2) > 0 {
|
||||
actual[string(r.RawV2)] = struct{}{}
|
||||
} else {
|
||||
actual[string(r.Raw)] = struct{}{}
|
||||
}
|
||||
}
|
||||
expected := make(map[string]struct{}, len(test.want))
|
||||
for _, v := range test.want {
|
||||
expected[v] = struct{}{}
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
104
pkg/detectors/hubspot_apikey/v2/hubspot_apikey_v2.go
Normal file
104
pkg/detectors/hubspot_apikey/v2/hubspot_apikey_v2.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
// "log"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (s Scanner) Version() int { return 2 }
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ interface {
|
||||
detectors.Detector
|
||||
detectors.Versioner
|
||||
} = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
defaultClient = common.SaneHttpClient()
|
||||
|
||||
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`)
|
||||
)
|
||||
|
||||
// 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{"pat-na1-", "pat-eu1-"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify HubSpotApiKey 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)
|
||||
|
||||
uniqueMatches := make(map[string]struct{})
|
||||
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
|
||||
uniqueMatches[match[1]] = struct{}{}
|
||||
}
|
||||
|
||||
for token := range uniqueMatches {
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_HubSpotApiKey,
|
||||
Raw: []byte(token),
|
||||
Redacted: token[8:] + "...",
|
||||
}
|
||||
|
||||
if verify {
|
||||
client := s.client
|
||||
if client == nil {
|
||||
client = defaultClient
|
||||
}
|
||||
|
||||
verified, verificationErr := verifyToken(ctx, client, token)
|
||||
s1.Verified = verified
|
||||
s1.SetVerificationError(verificationErr)
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.hubapi.com/account-info/v3/api-usage/daily/private-apps", nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("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
|
||||
case http.StatusForbidden:
|
||||
// The token is valid but lacks permission for the endpoint.
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_HubSpotApiKey
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package hubspotapikeyv2
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
82
pkg/detectors/hubspot_apikey/v2/hubspot_apikey_v2_test.go
Normal file
82
pkg/detectors/hubspot_apikey/v2/hubspot_apikey_v2_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
|
||||
)
|
||||
|
||||
func TestHubspotV2_Pattern(t *testing.T) {
|
||||
d := Scanner{}
|
||||
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "eu key",
|
||||
input: `
|
||||
const private_app_token = 'pat-eu1-1457aed5-04c6-40e2-83ad-a862d3cf19f2';
|
||||
|
||||
app.get('/homepage', async (req, res) => {
|
||||
const contactsEndpoint = 'https://api.hubspot.com/crm/v3/objects/contacts';`,
|
||||
want: []string{"pat-eu1-1457aed5-04c6-40e2-83ad-a862d3cf19f2"},
|
||||
},
|
||||
{
|
||||
name: "na key",
|
||||
input: `hubspot:
|
||||
api:
|
||||
url: https://api.hubapi.com
|
||||
auth-token: pat-na1-ffbb9f50-d96b-4abc-84f1-b986617be1b5
|
||||
subscriptions:`,
|
||||
want: []string{"pat-na1-ffbb9f50-d96b-4abc-84f1-b986617be1b5"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
|
||||
if len(matchedDetectors) == 0 {
|
||||
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := d.FromData(context.Background(), false, []byte(test.input))
|
||||
if err != nil {
|
||||
t.Errorf("error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(results) != len(test.want) {
|
||||
if len(results) == 0 {
|
||||
t.Errorf("did not receive result")
|
||||
} else {
|
||||
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
actual := make(map[string]struct{}, len(results))
|
||||
for _, r := range results {
|
||||
if len(r.RawV2) > 0 {
|
||||
actual[string(r.RawV2)] = struct{}{}
|
||||
} else {
|
||||
actual[string(r.Raw)] = struct{}{}
|
||||
}
|
||||
}
|
||||
expected := make(map[string]struct{}, len(test.want))
|
||||
for _, v := range test.want {
|
||||
expected[v] = struct{}{}
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package hubspotapikeyv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
// "log"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Scanner struct{}
|
||||
|
||||
func (s Scanner) Version() int { return 2 }
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
var _ detectors.Versioner = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
client = common.SaneHttpClient()
|
||||
|
||||
keyPat = regexp.MustCompile(`\b(pat-na1-[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`)
|
||||
)
|
||||
|
||||
// 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{"pat-na1-"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify HubSpotApiKey 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)
|
||||
for _, match := range matches {
|
||||
if len(match) != 2 {
|
||||
continue
|
||||
}
|
||||
resMatch := strings.TrimSpace(match[1])
|
||||
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_HubSpotApiKey,
|
||||
Raw: []byte(resMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.hubapi.com/account-info/v3/api-usage/daily/private-apps", 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_HubSpotApiKey
|
||||
}
|
|
@ -335,8 +335,8 @@ import (
|
|||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/honeycomb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/host"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/html2pdf"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspotapikey"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspotapikeyv2"
|
||||
hubspot_apikey_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v1"
|
||||
hubspot_apikey_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v2"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/humanity"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hunter"
|
||||
|
@ -864,7 +864,7 @@ func DefaultDetectors() []detectors.Detector {
|
|||
&rapidapi.Scanner{},
|
||||
&discordbottoken.Scanner{},
|
||||
&netlify.Scanner{},
|
||||
&hubspotapikey.Scanner{},
|
||||
&hubspot_apikey_v1.Scanner{},
|
||||
&travisci.Scanner{},
|
||||
&scalewaykey.Scanner{},
|
||||
&fastlypersonaltoken.Scanner{},
|
||||
|
@ -1633,7 +1633,8 @@ func DefaultDetectors() []detectors.Detector {
|
|||
netsuite.Scanner{},
|
||||
robinhoodcrypto.Scanner{},
|
||||
nvapi.Scanner{},
|
||||
hubspotapikeyv2.Scanner{},
|
||||
hubspot_apikey_v1.Scanner{},
|
||||
&hubspot_apikey_v2.Scanner{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue