feat(detectors): add Resend detector (#5103)
Adds a TruffleHog detector for Resend (https://resend.com), an email API for transactional and marketing email. Key format: re_[1-9A-HJ-NP-Za-km-z]{8}_[1-9A-HJ-NP-Za-km-z]{24} (base58 alphabet, excluding the ambiguous characters 0, O, I, and l). Co-authored-by: Shahzad Haider <[email protected]> Co-authored-by: meredith <[email protected]>
This commit is contained in:
co-authored by
Shahzad Haider
meredith
parent
54ff78b9a8
commit
e58aec687e
@@ -580,6 +580,7 @@ func run(state overseer.State, logSync func() error) {
|
||||
feature.NewRelicMobileAppTokenDetectorEnabled.Store(true)
|
||||
feature.MSTeamsWebhookV2DetectorEnabled.Store(true)
|
||||
feature.SolarwindsDetectorEnabled.Store(true)
|
||||
feature.ResendDetectorEnabled.Store(true)
|
||||
feature.WeightsAndBiasesV2DetectorEnabled.Store(true)
|
||||
feature.HumioAPITokenDetectorEnabled.Store(true)
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package resend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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/detector_typepb"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
|
||||
// GET /api-keys verifies both Resend permission tiers with a single
|
||||
// read-only request:
|
||||
// - full_access -> 200
|
||||
// - sending_access -> 401 with body {"name":"restricted_api_key"}
|
||||
//
|
||||
// See https://resend.com/docs/api-reference/api-keys/list-api-keys and
|
||||
// https://resend.com/docs/api-reference/errors.
|
||||
const verifyURL = "https://api.resend.com/api-keys"
|
||||
|
||||
var (
|
||||
defaultClient = common.SaneHttpClient()
|
||||
|
||||
// Resend API keys: prefix `re_`, 8 chars of base58,
|
||||
// underscore, then 24 chars from the same alphabet.
|
||||
keyPat = regexp.MustCompile(`\b(re_[1-9A-HJ-NP-Za-km-z]{8}_[1-9A-HJ-NP-Za-km-z]{24})\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{"re_"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify Resend 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: detector_typepb.DetectorType_Resend,
|
||||
Raw: []byte(token),
|
||||
SecretParts: map[string]string{"key": token},
|
||||
}
|
||||
|
||||
if verify {
|
||||
client := s.getClient()
|
||||
isVerified, extraData, verificationErr := verifyMatch(ctx, client, token)
|
||||
s1.Verified = isVerified
|
||||
s1.ExtraData = extraData
|
||||
s1.SetVerificationError(verificationErr, token)
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) getClient() *http.Client {
|
||||
if s.client != nil {
|
||||
return s.client
|
||||
}
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
type resendError struct {
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, verifyURL, nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
extraData := map[string]string{
|
||||
"rotation_guide": "https://resend.com/docs/dashboard/api-keys/introduction",
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
// Active key with `full_access` permission.
|
||||
extraData["permission"] = "full_access"
|
||||
return true, extraData, nil
|
||||
|
||||
case http.StatusUnauthorized:
|
||||
// Resend returns 401 for two distinct cases. We must look at the
|
||||
// error name to tell them apart:
|
||||
// - restricted_api_key: active `sending_access` key trying to
|
||||
// reach a non-send route. The key is real and can send mail.
|
||||
// - missing_api_key: no Authorization header (should not
|
||||
// happen here since we set one above).
|
||||
var body resendError
|
||||
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
|
||||
return false, nil, fmt.Errorf("failed to decode 401 response body: %w", err)
|
||||
}
|
||||
if body.Name == "restricted_api_key" {
|
||||
extraData["permission"] = "sending_access"
|
||||
return true, extraData, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
|
||||
case http.StatusBadRequest, http.StatusForbidden:
|
||||
// 400 validation_error: unknown, malformed, or soft-deleted key.
|
||||
// 403 suspended_api_key / restricted_api_key ("not active"):
|
||||
// the key exists but is already disabled; no rotation needed.
|
||||
return false, nil, nil
|
||||
|
||||
default:
|
||||
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detector_typepb.DetectorType {
|
||||
return detector_typepb.DetectorType_Resend
|
||||
}
|
||||
|
||||
func (s Scanner) Description() string {
|
||||
return "Resend is an email API for transactional and marketing email. Resend API keys can be used to send email, manage domains and audiences, and access other resources on the account."
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package resend
|
||||
|
||||
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/detector_typepb"
|
||||
)
|
||||
|
||||
func TestResend_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("RESEND_API_KEY")
|
||||
sendingAccessSecret := testSecrets.MustGetField("RESEND_API_KEY_SENDING_ACCESS")
|
||||
inactiveSecret := testSecrets.MustGetField("RESEND_API_KEY_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 (full_access)",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a resend secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Resend,
|
||||
Verified: true,
|
||||
ExtraData: map[string]string{
|
||||
"permission": "full_access",
|
||||
"rotation_guide": "https://resend.com/docs/dashboard/api-keys/introduction",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, verified (sending_access)",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a resend secret %s within", sendingAccessSecret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Resend,
|
||||
Verified: true,
|
||||
ExtraData: map[string]string{
|
||||
"permission": "sending_access",
|
||||
"rotation_guide": "https://resend.com/docs/dashboard/api-keys/introduction",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, unverified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a resend secret %s within but not valid", inactiveSecret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Resend,
|
||||
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 resend secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Resend,
|
||||
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 resend secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Resend,
|
||||
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("Resend.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.Errorf("Resend.FromData() error = %v, wantVerificationErr %v", got[i].VerificationError(), tt.wantVerificationErr)
|
||||
}
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "SecretParts")
|
||||
ignoreUnexported := cmpopts.IgnoreUnexported(detectors.Result{})
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts, ignoreUnexported); diff != "" {
|
||||
t.Errorf("Resend.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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package resend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
|
||||
)
|
||||
|
||||
var (
|
||||
validPattern = "re_1234abcd_aBcDeFgHiJkLmNoPqRsTuVwX"
|
||||
invalidPattern = "re_1234abcd_aBcDeFgHiJk0OIlmNoPqRsTu"
|
||||
keyword = "resend"
|
||||
)
|
||||
|
||||
func TestResend_Pattern(t *testing.T) {
|
||||
d := Scanner{}
|
||||
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "valid pattern - with keyword resend",
|
||||
input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
|
||||
want: []string{validPattern},
|
||||
},
|
||||
{
|
||||
name: "valid pattern - ignore duplicate",
|
||||
input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
|
||||
want: []string{validPattern},
|
||||
},
|
||||
{
|
||||
name: "invalid pattern - contains disallowed base58 chars (0/O/I/l)",
|
||||
input: fmt.Sprintf("%s = '%s'", keyword, invalidPattern),
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "invalid pattern - wrong length",
|
||||
input: fmt.Sprintf("%s = 're_1234abcd_short'", keyword),
|
||||
want: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -645,6 +645,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replicate"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replyio"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/requestfinance"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/resend"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackhtmltopdfapi"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackscreenshotapi"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rev"
|
||||
@@ -1567,6 +1568,7 @@ func buildDetectorList() []detectors.Detector {
|
||||
&replicate.Scanner{},
|
||||
&replyio.Scanner{},
|
||||
&requestfinance.Scanner{},
|
||||
&resend.Scanner{},
|
||||
// &restpack.Scanner{},
|
||||
&restpackhtmltopdfapi.Scanner{},
|
||||
&restpackscreenshotapi.Scanner{},
|
||||
@@ -1898,6 +1900,8 @@ func buildDetectorList() []detectors.Detector {
|
||||
return !feature.SolarwindsDetectorEnabled.Load()
|
||||
case *humioapitoken.Scanner:
|
||||
return !feature.HumioAPITokenDetectorEnabled.Load()
|
||||
case *resend.Scanner:
|
||||
return !feature.ResendDetectorEnabled.Load()
|
||||
case *weightsandbiasesv2.Scanner:
|
||||
return !feature.WeightsAndBiasesV2DetectorEnabled.Load()
|
||||
default:
|
||||
|
||||
@@ -152,6 +152,7 @@ var excludedFromDefaultList = map[detector_typepb.DetectorType]struct{}{
|
||||
detector_typepb.DetectorType_NewRelicMobileAppToken: {},
|
||||
detector_typepb.DetectorType_SolarWindsObservability: {},
|
||||
detector_typepb.DetectorType_HumioAPIToken: {},
|
||||
detector_typepb.DetectorType_Resend: {},
|
||||
|
||||
// Reserved / special types.
|
||||
detector_typepb.DetectorType_CustomRegex: {}, // added dynamically via engine config, not via buildDetectorList()
|
||||
|
||||
@@ -50,6 +50,7 @@ var (
|
||||
NewRelicMobileAppTokenDetectorEnabled atomic.Bool
|
||||
MSTeamsWebhookV2DetectorEnabled atomic.Bool
|
||||
SolarwindsDetectorEnabled atomic.Bool
|
||||
ResendDetectorEnabled atomic.Bool
|
||||
WeightsAndBiasesV2DetectorEnabled atomic.Bool
|
||||
HumioAPITokenDetectorEnabled atomic.Bool
|
||||
)
|
||||
|
||||
@@ -1126,6 +1126,7 @@ const (
|
||||
DetectorType_NewRelicMobileAppToken DetectorType = 1068
|
||||
DetectorType_SolarWindsObservability DetectorType = 1069
|
||||
DetectorType_HumioAPIToken DetectorType = 1070
|
||||
DetectorType_Resend DetectorType = 1071
|
||||
)
|
||||
|
||||
// Enum value maps for DetectorType.
|
||||
@@ -2198,6 +2199,7 @@ var (
|
||||
1068: "NewRelicMobileAppToken",
|
||||
1069: "SolarWindsObservability",
|
||||
1070: "HumioAPIToken",
|
||||
1071: "Resend",
|
||||
}
|
||||
DetectorType_value = map[string]int32{
|
||||
"Alibaba": 0,
|
||||
@@ -3267,6 +3269,7 @@ var (
|
||||
"NewRelicMobileAppToken": 1068,
|
||||
"SolarWindsObservability": 1069,
|
||||
"HumioAPIToken": 1070,
|
||||
"Resend": 1071,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3302,7 +3305,7 @@ var File_detector_type_proto protoreflect.FileDescriptor
|
||||
var file_detector_type_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
||||
0x74, 0x79, 0x70, 0x65, 0x2a, 0x87, 0x8c, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74,
|
||||
0x74, 0x79, 0x70, 0x65, 0x2a, 0x94, 0x8c, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62,
|
||||
0x61, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a,
|
||||
0x03, 0x41, 0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10,
|
||||
@@ -4422,12 +4425,13 @@ var file_detector_type_proto_rawDesc = []byte{
|
||||
0x6c, 0x65, 0x41, 0x70, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xac, 0x08, 0x12, 0x1c, 0x0a,
|
||||
0x17, 0x53, 0x6f, 0x6c, 0x61, 0x72, 0x57, 0x69, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x73, 0x65, 0x72,
|
||||
0x76, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x10, 0xad, 0x08, 0x12, 0x12, 0x0a, 0x0d, 0x48,
|
||||
0x75, 0x6d, 0x69, 0x6f, 0x41, 0x50, 0x49, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xae, 0x08, 0x42,
|
||||
0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72,
|
||||
0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72,
|
||||
0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
|
||||
0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65,
|
||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x75, 0x6d, 0x69, 0x6f, 0x41, 0x50, 0x49, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xae, 0x08, 0x12,
|
||||
0x0b, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x10, 0xaf, 0x08, 0x42, 0x41, 0x5a, 0x3f,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66,
|
||||
0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66,
|
||||
0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f,
|
||||
0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x70, 0x62, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -1072,4 +1072,5 @@ enum DetectorType {
|
||||
NewRelicMobileAppToken = 1068;
|
||||
SolarWindsObservability = 1069;
|
||||
HumioAPIToken = 1070;
|
||||
Resend = 1071;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user