diff --git a/pkg/detectors/sendgrid/sendgrid.go b/pkg/detectors/sendgrid/sendgrid.go index 6b73c1c08..0a4045b7b 100644 --- a/pkg/detectors/sendgrid/sendgrid.go +++ b/pkg/detectors/sendgrid/sendgrid.go @@ -13,13 +13,15 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/common" ) -type Scanner struct{} +type Scanner struct { + client *http.Client +} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClient() + defaultClient = common.SaneHttpClient() keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sendgrid"}) + `(SG\.[\w\-_]{20,24}\.[\w\-_]{39,50})\b`) ) @@ -42,7 +44,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } res := strings.TrimSpace(match[1]) - s := detectors.Result{ + s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_SendGrid, Raw: []byte(res), } @@ -52,6 +54,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result // 403 will be issued if the scope is wrong but the key is correct baseURL := "https://api.sendgrid.com/v3/templates" + client := s.client + if client == nil { + client = defaultClient + } + // test `read_user` scope req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil) if err != nil { @@ -67,16 +74,18 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result // 403 means good key but not the right scope // 401 is bad key if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusForbidden { - s.Verified = true + s1.Verified = true + } else if res.StatusCode != http.StatusUnauthorized { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } } } - if !s.Verified && detectors.IsKnownFalsePositive(string(s.Raw), detectors.DefaultFalsePositives, true) { + if !s1.Verified && detectors.IsKnownFalsePositive(string(s1.Raw), detectors.DefaultFalsePositives, true) { continue } - results = append(results, s) + results = append(results, s1) } return diff --git a/pkg/detectors/sendgrid/sendgrid_test.go b/pkg/detectors/sendgrid/sendgrid_test.go index 15cebf037..bd95329da 100644 --- a/pkg/detectors/sendgrid/sendgrid_test.go +++ b/pkg/detectors/sendgrid/sendgrid_test.go @@ -9,7 +9,8 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "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" @@ -18,7 +19,7 @@ import ( func TestSendgrid_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2") + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } @@ -30,11 +31,12 @@ func TestSendgrid_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", @@ -50,7 +52,8 @@ func TestSendgrid_FromChunk(t *testing.T) { Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", @@ -66,7 +69,8 @@ func TestSendgrid_FromChunk(t *testing.T) { Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -76,22 +80,54 @@ func TestSendgrid_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + 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 sendgrid secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SendGrid, + 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 sendgrid secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SendGrid, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { t.Errorf("Sendgrid.FromData() error = %v, wantErr %v", err, tt.wantErr) return } - for i := range got { - got[i].Raw = nil - } - if diff := pretty.Compare(got, tt.want); diff != "" { + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("Sendgrid.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } })