Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
98b545e
added make.com api_token detector
Jeff-Rowell Jul 26, 2025
7a26c6f
updated make.com api_token keywords
Jeff-Rowell Jul 27, 2025
ceac003
added make.com mcp_token detector
Jeff-Rowell Jul 27, 2025
e5d369d
Merge branch 'trufflesecurity:main' into feat/make-detector
Jeff-Rowell Jul 27, 2025
bcfbe0b
added matching for make.com API URLs and use the found URLs for verif…
Jeff-Rowell Jul 27, 2025
04c28e6
changed detector type nums
Jeff-Rowell Jul 28, 2025
e1187a9
feat: add webexbot support (#4322)
jonathongardner Jul 28, 2025
33026a3
Regenerate protobufs with the correct protoc (etc) version (#4349)
camgunz Jul 28, 2025
d859144
fix test (#4350)
dustin-decker Jul 28, 2025
36fd8fa
[Detector]-Detector for tableau personal access token (#4261)
SyedAliHamad Jul 28, 2025
fa2aa99
changed detector type nums
Jeff-Rowell Jul 28, 2025
29aefbd
Merge branch 'main' into feat/make-detector
Jeff-Rowell Jul 29, 2025
e65ec10
make protos
Jeff-Rowell Jul 29, 2025
9b3bce4
Merge branch 'main' into feat/make-detector
kashifkhan0771 Jul 31, 2025
bfd263e
implemented EndpointCustomizer interface
Jeff-Rowell Aug 2, 2025
b037651
Merge branch 'main' into feat/make-detector
Jeff-Rowell Aug 2, 2025
f6d3b51
- added make api token to exception list next to tableau and artifactory
Jeff-Rowell Aug 2, 2025
17f9659
Merge branch 'main' into feat/make-detector
kashifkhan0771 Aug 4, 2025
4203204
Merge branch 'main' into feat/make-detector
amanfcp Aug 4, 2025
13f4b89
Update pkg/detectors/make/api_token/api_token.go
Jeff-Rowell Aug 8, 2025
f977a10
Merge branch 'main' into feat/make-detector
Jeff-Rowell Aug 8, 2025
31dce6b
- updated make endpoint regex
Jeff-Rowell Aug 8, 2025
017112b
make protos
Jeff-Rowell Aug 8, 2025
1a51dd2
Merge branch 'main' into feat/make-detector
amanfcp Aug 11, 2025
8fce583
Merge branch 'main' into feat/make-detector
kashifkhan0771 Aug 13, 2025
74acac4
- deduplicated found make.com urls
Jeff-Rowell Aug 14, 2025
3cc083f
Merge branch 'main' into feat/make-detector
kashifkhan0771 Aug 15, 2025
14b6008
Added defer
kashifkhan0771 Aug 15, 2025
9f32ec8
Made a few minor updates per review:
Jeff-Rowell Sep 5, 2025
8fb98a5
Merge branch 'main' into feat/make-detector
Jeff-Rowell Sep 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions pkg/detectors/make/api_token/api_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package api_token

import (
"context"
"fmt"
"io"
"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
}

// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"make"}) + `\b([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\b`)
// Pattern to match Make.com URLs in the data
urlPat = regexp.MustCompile(`\bhttps://(eu[12]|us[12])\.make\.(?:com|celonis\.com)/api/v2/`)
)

// 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{"make"}
}

// FromData will find and optionally verify Make 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{}{}
}

// Extract Make URLs from the data
var foundURLs []string
for _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {
foundURLs = append(foundURLs, match[0])
}

// If URLs were found, create combinations of tokens and URLs
if len(foundURLs) > 0 {
for match := range uniqueMatches {
for _, foundURL := range foundURLs {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Raw: []byte(match),
RawV2: []byte(match + ":" + foundURL),
}

if verify {
client := s.client
if client == nil {
client = defaultClient
}

isVerified, extraData, verificationErr := verifyMatch(ctx, client, match, foundURLs)
s1.Verified = isVerified
s1.ExtraData = extraData
s1.SetVerificationError(verificationErr, match)
}

results = append(results, s1)
}
}
} else {
// No URLs found, just return tokens
for match := range uniqueMatches {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Raw: []byte(match),
}

if verify {
client := s.client
if client == nil {
client = defaultClient
}

isVerified, extraData, verificationErr := verifyMatch(ctx, client, match, foundURLs)
s1.Verified = isVerified
s1.ExtraData = extraData
s1.SetVerificationError(verificationErr, match)
}

results = append(results, s1)
}
}

return
}

func verifyMatch(ctx context.Context, client *http.Client, token string, foundURLs []string) (bool, map[string]string, error) {
baseURLs := []string{
"https://eu1.make.com/api/v2/",
"https://eu2.make.com/api/v2/",
"https://us1.make.com/api/v2/",
"https://us2.make.com/api/v2/",
"https://us1.make.celonis.com/api/v2/",
"https://eu1.make.celonis.com/api/v2/",
}


// If we found URLs in the data, try those first
if len(foundURLs) > 0 {
for _, foundURL := range foundURLs {
verified, err := tryURL(ctx, client, foundURL, token)
if verified {
return true, nil, nil
}
if err != nil {
// If the matched URL failed, continue to try all base URLs
break
}
// If the matched URL returned a determinate failure (401), we still try other base URLs
}
}

// Try all base URLs
var lastErr error
for _, baseURL := range baseURLs {
verified, err := tryURL(ctx, client, baseURL, token)
if verified {
return true, nil, nil
}
if err != nil {
lastErr = err
continue
}
// Continue to next URL on determinate failures (401)
}

// If we got here, either all endpoints failed or we had errors
if lastErr != nil {
return false, nil, lastErr
}
return false, nil, nil
}

func tryURL(ctx context.Context, client *http.Client, baseURL, token string) (bool, error) {
// This endpoint returns 200 OK if the token is valid and the correct FQDN for the API is used.
// https://developers.make.com/api-documentation/api-reference/users-greater-than-me#get-users-me-current-authorization
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"users/me/current-authorization", nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", "Token "+token)

res, err := client.Do(req)
if err != nil {
return false, err
}

func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()

switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
// Determinate failure - invalid token
return false, nil
default:
// Indeterminate failure - unexpected response
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
}

func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_MakeApiToken
}

func (s Scanner) Description() string {
return "Make.com is a low-code/no-code automation platform that allows users to connect apps and services typically to automate business workflows. This detector identifies API tokens used for Make.com integrations."
}
161 changes: 161 additions & 0 deletions pkg/detectors/make/api_token/api_token_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//go:build detectors
// +build detectors

package api_token

import (
"context"
"fmt"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

func TestMake_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("MAKE_API_TOKEN")
inactiveSecret := testSecrets.MustGetField("MAKE_API_TOKEN_INACTIVE")

type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a make secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a make secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a make secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, verified but unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a make secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_MakeApiToken,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Make.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Make.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}

func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
Loading