feat(detectors): create azure refresh token (#2978)
This commit is contained in:
@@ -24,7 +24,8 @@ var (
|
||||
// https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq?view=o365-worldwide#why-do-i-have-an--onmicrosoft-com--domain
|
||||
tenantIdPat = regexp.MustCompile(fmt.Sprintf(
|
||||
//language=regexp
|
||||
`(?i)(?:(?:login\.microsoftonline\.com/|(?:login|sts)\.windows\.net/|(?:t[ae]n[ae]nt(?:[ ._-]?id)?|\btid)(?:.|\s){0,60}?)(%s)|https?://(%s)|X-AnchorMailbox(?:.|\s){0,60}?@(%s))`,
|
||||
`(?i)(?:(?:login\.microsoftonline\.com/|(?:login|sts)\.windows\.net/|(?:t[ae]n[ae]nt(?:[ ._-]?id)?|\btid)(?:.|\s){0,60}?)(%s)|https?://(%s)|X-AnchorMailbox(?:.|\s){0,60}?@(%s)|/(%s)/(?:oauth2/v2\.0|B2C_1\w+|common|discovery|federationmetadata|kerberos|login|openid/|reprocess|resume|saml2|token|uxlogout|v2\.0|wsfed))`,
|
||||
uuidStr,
|
||||
uuidStr,
|
||||
uuidStr,
|
||||
uuidStr,
|
||||
@@ -47,9 +48,13 @@ func FindTenantIdMatches(data string) map[string]struct{} {
|
||||
m = strings.ToLower(match[2])
|
||||
} else if match[3] != "" {
|
||||
m = strings.ToLower(match[3])
|
||||
} else if match[4] != "" {
|
||||
m = strings.ToLower(match[4])
|
||||
}
|
||||
if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
|
||||
continue
|
||||
} else if detectors.StringShannonEntropy(m) < 3 {
|
||||
continue
|
||||
}
|
||||
uniqueMatches[m] = struct{}{}
|
||||
}
|
||||
@@ -66,6 +71,8 @@ func FindClientIdMatches(data string) map[string]struct{} {
|
||||
m := strings.ToLower(match[1])
|
||||
if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
|
||||
continue
|
||||
} else if detectors.StringShannonEntropy(m) < 3 {
|
||||
continue
|
||||
}
|
||||
uniqueMatches[m] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type testCase struct {
|
||||
}
|
||||
|
||||
func runPatTest(t *testing.T, tests map[string]testCase, matchFunc func(data string) map[string]struct{}) {
|
||||
t.Helper()
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
matches := matchFunc(test.Input)
|
||||
@@ -122,6 +123,41 @@ tenant_id = "57aabdfc-6ce0-4828-94a2-9abe277892ec"`,
|
||||
"974fde14-c3a4-481b-9b03-cfce182c3a07": {},
|
||||
},
|
||||
},
|
||||
"oauth paths": {
|
||||
Input: ` "authPath": "/9b4bfaea-dd1c-4add-b1de-e10f51c65fd3/oauth2/v2.0/authorize",
|
||||
/32896ed7-d559-401b-85cf-167143d61be0/B2C_1A_Tapio_Signin/v2.0
|
||||
/461858f4-9c0d-46e0-a9e6-aefc4889aad6/B2C_1_sign_up_or_sign_in/SelfAsserted?tx=S
|
||||
-ArgumentList "/3f548be2-31e9-4681-839e-bc80d461f367/common/oauth2/authorize"
|
||||
"jwks_uri": "/6babcaad-604b-40ac-a9d7-9fd97c0b779f/discovery/keys",
|
||||
MetadataLocation = "/b55f0c51-61a7-45c3-84df-33569b247796/federationmetadata/2007-06/federationmetadata.xml?appid=3245199b-1a5d-42df-93ce-e64ac7f5b938
|
||||
"kerberos_endpoint": "/a4067d12-2fc0-4367-a213-9e4031cbc173/kerberos",
|
||||
/b2326b8a-059d-48ca-96ac-8d8d5d841860/login
|
||||
"userinfo_endpoint": "/6ba4caad-604b-40ac-a9d7-9fd97c0b779f/openid/userinfo"
|
||||
…en-US","urlLogin":"/9673e9a8-aa57-4461-9336-5fd3f0034e18/reprocess?ctx=rQIIAZ2QvWvbQA…
|
||||
/6c912b97-d9f0-4472-a96a-d82de2f1d438/resume?ctx=rQIIAZVTP
|
||||
// /aa8306d8-5417-43cc-b8e8-7e77b918682c/v2.0/.well-known/openid-configuration
|
||||
// /051aeb51-408b-403b-b95c-4ff3b303a08a/token
|
||||
"/4a5378f9-29f4-4d3e-be89-669d03ada9d8/uxlogout"
|
||||
/dc38a67a-f981-4e24-ba16-4443ada44484/wsfed
|
||||
`,
|
||||
Expected: map[string]struct{}{
|
||||
"051aeb51-408b-403b-b95c-4ff3b303a08a": {},
|
||||
"32896ed7-d559-401b-85cf-167143d61be0": {},
|
||||
"3f548be2-31e9-4681-839e-bc80d461f367": {},
|
||||
"461858f4-9c0d-46e0-a9e6-aefc4889aad6": {},
|
||||
"4a5378f9-29f4-4d3e-be89-669d03ada9d8": {},
|
||||
"6ba4caad-604b-40ac-a9d7-9fd97c0b779f": {},
|
||||
"6babcaad-604b-40ac-a9d7-9fd97c0b779f": {},
|
||||
"6c912b97-d9f0-4472-a96a-d82de2f1d438": {},
|
||||
"9673e9a8-aa57-4461-9336-5fd3f0034e18": {},
|
||||
"9b4bfaea-dd1c-4add-b1de-e10f51c65fd3": {},
|
||||
"a4067d12-2fc0-4367-a213-9e4031cbc173": {},
|
||||
"aa8306d8-5417-43cc-b8e8-7e77b918682c": {},
|
||||
"b2326b8a-059d-48ca-96ac-8d8d5d841860": {},
|
||||
"b55f0c51-61a7-45c3-84df-33569b247796": {},
|
||||
"dc38a67a-f981-4e24-ba16-4443ada44484": {},
|
||||
},
|
||||
},
|
||||
"x-anchor-mailbox": {
|
||||
// The tenantID can be encoded in this parameter.
|
||||
// https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/95a63a7fe97d91b99979e5bf78e03f6acf40a286/msal/application.py#L185-L186
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
package refreshtoken
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
client *http.Client
|
||||
detectors.DefaultMultiPartCredentialProvider
|
||||
}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ interface {
|
||||
detectors.Detector
|
||||
detectors.MaxSecretSizeProvider
|
||||
detectors.StartOffsetProvider
|
||||
} = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
defaultClient = common.SaneHttpClient()
|
||||
|
||||
refreshTokenPat = regexp.MustCompile(`\b[01]\.A[\w-]{50,}(?:\.\d)?\.Ag[\w-]{250,}(?:\.A[\w-]{200,})?`)
|
||||
)
|
||||
|
||||
// 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{"0.A", "1.A"}
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AzureRefreshToken
|
||||
}
|
||||
|
||||
func (s Scanner) Description() string {
|
||||
return "Azure Entra ID refresh tokens provide long-lasting access to an account."
|
||||
}
|
||||
|
||||
func (Scanner) MaxSecretSize() int64 { return 2048 }
|
||||
|
||||
func (Scanner) StartOffset() int64 { return 4096 }
|
||||
|
||||
// FromData will find and optionally verify Azure RefreshToken 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)
|
||||
|
||||
tokenMatches := findTokenMatches(dataStr)
|
||||
if len(tokenMatches) == 0 {
|
||||
return
|
||||
}
|
||||
clientMatches := azure_entra.FindClientIdMatches(dataStr)
|
||||
if len(clientMatches) == 0 {
|
||||
clientMatches[defaultClientId] = struct{}{}
|
||||
}
|
||||
tenantMatches := azure_entra.FindTenantIdMatches(dataStr)
|
||||
if len(tenantMatches) == 0 {
|
||||
tenantMatches[defaultTenantId] = struct{}{}
|
||||
}
|
||||
|
||||
return s.processMatches(ctx, tokenMatches, clientMatches, tenantMatches, verify), err
|
||||
}
|
||||
|
||||
func (s Scanner) processMatches(ctx context.Context, refreshTokens, clientIds, tenantIds map[string]struct{}, verify bool) (results []detectors.Result) {
|
||||
logCtx := logContext.AddLogger(ctx)
|
||||
invalidClientsForTenant := make(map[string]map[string]struct{})
|
||||
validTenants := make(map[string]struct{})
|
||||
|
||||
TokenLoop:
|
||||
for token := range refreshTokens {
|
||||
var (
|
||||
r *detectors.Result
|
||||
clientId string
|
||||
tenantId string
|
||||
)
|
||||
|
||||
ClientLoop:
|
||||
for cId := range clientIds {
|
||||
clientId = cId
|
||||
for tId := range tenantIds {
|
||||
tenantId = tId
|
||||
|
||||
// Skip known invalid tenants.
|
||||
invalidClients := invalidClientsForTenant[tenantId]
|
||||
if invalidClients == nil {
|
||||
invalidClients = map[string]struct{}{}
|
||||
invalidClientsForTenant[tenantId] = invalidClients
|
||||
}
|
||||
if _, ok := invalidClients[clientId]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if verify {
|
||||
client := s.client
|
||||
if client == nil {
|
||||
client = defaultClient
|
||||
}
|
||||
|
||||
if _, ok := validTenants[tenantId]; !ok {
|
||||
if azure_entra.TenantExists(logCtx, client, tenantId) {
|
||||
validTenants[tenantId] = struct{}{}
|
||||
} else {
|
||||
delete(tenantIds, tenantId)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
isVerified, extraData, verificationErr := verifyMatch(ctx, client, token, clientId, tenantId)
|
||||
// Handle errors.
|
||||
if verificationErr != nil {
|
||||
if errors.Is(verificationErr, ErrTenantNotFound) {
|
||||
// Tenant doesn't exist. This shouldn't happen with the check above.
|
||||
delete(tenantIds, tenantId)
|
||||
continue
|
||||
} else if errors.Is(verificationErr, ErrClientNotFoundInTenant) {
|
||||
// Tenant is valid but the ClientID doesn't exist.
|
||||
invalidClients[clientId] = struct{}{}
|
||||
continue
|
||||
} else if errors.Is(verificationErr, ErrTokenExpired) {
|
||||
continue TokenLoop
|
||||
}
|
||||
}
|
||||
|
||||
// The result is verified or there's only one associated client and tenant.
|
||||
if isVerified {
|
||||
r = createResult(tenantId, clientId, token, isVerified, extraData, verificationErr)
|
||||
break ClientLoop
|
||||
}
|
||||
|
||||
// The result may be valid for another client/tenant.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
// Only include the clientId and tenantId if we're confident which one it is.
|
||||
if len(clientIds) != 1 || clientId == defaultClientId {
|
||||
clientId = ""
|
||||
}
|
||||
if len(tenantIds) != 1 || tenantId == defaultTenantId {
|
||||
tenantId = ""
|
||||
}
|
||||
r = createResult(token, clientId, tenantId, false, nil, nil)
|
||||
}
|
||||
|
||||
results = append(results, *r)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
const defaultTenantId = "common"
|
||||
const defaultClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c" // Microsoft Office
|
||||
|
||||
var (
|
||||
ErrTokenExpired = errors.New("token expired")
|
||||
ErrTenantNotFound = errors.New("tenant not found")
|
||||
ErrClientNotFoundInTenant = errors.New("application was not found in tenant")
|
||||
)
|
||||
|
||||
// https://learn.microsoft.com/en-us/advertising/guides/authentication-oauth-get-tokens?view=bingads-13#refresh-accesstoken
|
||||
func verifyMatch(ctx context.Context, client *http.Client, refreshToken string, clientId string, tenantId string) (bool, map[string]string, error) {
|
||||
data := url.Values{}
|
||||
data.Set("client_id", clientId)
|
||||
data.Set("scope", "https://graph.microsoft.com/.default")
|
||||
data.Set("refresh_token", refreshToken)
|
||||
data.Set("grant_type", "refresh_token")
|
||||
|
||||
tokenUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenUrl, bytes.NewBufferString(data.Encode()))
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
// Refresh token is valid.
|
||||
if res.StatusCode == http.StatusOK {
|
||||
var okResp successResponse
|
||||
if err := json.NewDecoder(res.Body).Decode(&okResp); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
extraData := map[string]string{
|
||||
"Tenant": tenantId,
|
||||
"Client": clientId,
|
||||
"Scope": okResp.Scope,
|
||||
}
|
||||
|
||||
// Add claims from the access token.
|
||||
token, _ := jwt.Parse(okResp.AccessToken, nil)
|
||||
if token != nil {
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
|
||||
if app := fmt.Sprint(claims["app_displayname"]); app != "" {
|
||||
extraData["Application"] = app
|
||||
}
|
||||
|
||||
// The user information can be in a few claims.
|
||||
switch {
|
||||
case claims["email"] != nil:
|
||||
extraData["User"] = fmt.Sprint(claims["email"])
|
||||
case claims["upn"] != nil:
|
||||
extraData["User"] = fmt.Sprint(claims["upn"])
|
||||
case claims["unique_name"]:
|
||||
extraData["User"] = fmt.Sprint(claims["unique_name"])
|
||||
}
|
||||
}
|
||||
return true, extraData, nil
|
||||
}
|
||||
|
||||
// Credentials *probably* aren't valid.
|
||||
var errResp errorResponse
|
||||
if err := json.NewDecoder(res.Body).Decode(&errResp); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
// Error codes can be looked up by removing the `AADSTS` prefix.
|
||||
// https://login.microsoftonline.com/error?code=9002313
|
||||
d := errResp.Description
|
||||
switch {
|
||||
case strings.HasPrefix(d, "AADSTS70008:"), strings.HasPrefix(d, "AADSTS700082:"):
|
||||
// https://login.microsoftonline.com/error?code=70008
|
||||
// https://login.microsoftonline.com/error?code=700082
|
||||
return false, nil, ErrTokenExpired
|
||||
case strings.HasPrefix(d, "AADSTS700016:"):
|
||||
// https://login.microsoftonline.com/error?code=700016
|
||||
return false, nil, ErrClientNotFoundInTenant
|
||||
case strings.HasPrefix(d, "AADSTS90002:"):
|
||||
// https://login.microsoftonline.com/error?code=90002
|
||||
return false, nil, ErrTenantNotFound
|
||||
case strings.HasPrefix(d, "AADSTS9002313"):
|
||||
// This seems to be a generic "invalid token" error code.
|
||||
// 'invalid_grant': AADSTS9002313: Invalid request. Request is malformed or invalid.
|
||||
return false, nil, nil
|
||||
default:
|
||||
return false, nil, fmt.Errorf("unexpected error '%s': %s", errResp.Error, errResp.Description)
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
// The secret is determinately not verified (nothing to do)
|
||||
return false, nil, nil
|
||||
default:
|
||||
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
type successResponse struct {
|
||||
Scope string `json:"scope"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type errorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Description string `json:"error_description"`
|
||||
}
|
||||
|
||||
// region Helper methods.
|
||||
func findTokenMatches(data string) map[string]struct{} {
|
||||
uniqueMatches := make(map[string]struct{})
|
||||
for _, match := range refreshTokenPat.FindAllStringSubmatch(data, -1) {
|
||||
m := match[0]
|
||||
if detectors.StringShannonEntropy(m) < 4 {
|
||||
continue
|
||||
}
|
||||
uniqueMatches[m] = struct{}{}
|
||||
}
|
||||
return uniqueMatches
|
||||
}
|
||||
|
||||
func createResult(refreshToken, clientId, tenantId string, verified bool, extraData map[string]string, err error) *detectors.Result {
|
||||
r := &detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_AzureRefreshToken,
|
||||
Raw: []byte(refreshToken),
|
||||
ExtraData: extraData,
|
||||
Verified: verified,
|
||||
}
|
||||
r.SetVerificationError(err, refreshToken)
|
||||
|
||||
if clientId != "" && tenantId != "" {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(`{`)
|
||||
sb.WriteString(`"refreshToken":"` + refreshToken + `"`)
|
||||
sb.WriteString(`,"clientId":"` + clientId + `"`)
|
||||
sb.WriteString(`,"tenantId":"` + tenantId + `"`)
|
||||
sb.WriteString(`}`)
|
||||
r.RawV2 = []byte(sb.String())
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -0,0 +1,161 @@
|
||||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package refreshtoken
|
||||
|
||||
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 TestRefreshToken_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("AZUREREFRESHTOKEN")
|
||||
inactiveSecret := testSecrets.MustGetField("AZUREREFRESHTOKEN_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 azurerefreshtoken secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_AzureRefreshToken,
|
||||
Verified: true,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantVerificationErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, unverified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a azurerefreshtoken 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_AzureRefreshToken,
|
||||
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 azurerefreshtoken secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_AzureRefreshToken,
|
||||
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 azurerefreshtoken secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_AzureRefreshToken,
|
||||
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("Azurerefreshtoken.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")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Azurerefreshtoken.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,151 @@
|
||||
package refreshtoken
|
||||
|
||||
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 TestRefreshToken_Pattern(t *testing.T) {
|
||||
d := Scanner{}
|
||||
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
// Valid - 0.
|
||||
{
|
||||
name: "valid - token only",
|
||||
input: `"refresh_token": "0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8smoWqJBpit_3P_ntszmbCH2-dGwpsamwQMbLl7QBa7tlfXH_NtpD1vNTGkacraUMyTM5lfg1AR1DLAxs-pNSpg8NfrHbNSRAIacCpOyqtU05Dg9l5LC7ZYwxT35dQWEK0EExLER-wxjW9DrDZNQV4J3Ktv1Z4ANT2N2rqAjPYqHTDPCCcOi980ptizeImgVYiVr37Ff0Hnr_lAi4Em0wGB7KDdu319sV9Sebe91FIRDs7GVvvv7GFvKjTeXJwHCpbhdqX4X2TRMryNrTNZ8QY7_Wa25MQm7v0qfFqDW_pRMxxohGhClSedZFnkzrreIhZ8ULJ9NCf8YENRHDP3LuOJP5gex-H0MUNsJQLxlDq3bH-i7Fz_cTEB3UN_bvgE9aNe-5gal-ykO_gSx-Kk5D-vZWpLDrFUdRSGYHmKr1zgEZvQjsFUj8pGWgUwssqN9SOPxTYIEzQaxPAul5AFKcxGYt2l4Kvhh58txUdayFAglWrkx1lrxnpIcjoRmHOo45AKlgH30bVOjjltwvD4L9SGMAHhni3F6mCB6aNLGpYCHjrbdsiWolHKV0leJmBYl2Ye4eosQf9YYdgPAbCQKqOJ6gfrxJJTcfrISqDVw1c6C9qPPdHbvdol_KfdJntyfuPpHovx7AfARBcjb6nMgYRBI0wFWsGuTNDcylicMFRcZx6v283wBv4U_0PrG1_Yd5ktfgaTVXF733C-ma_-s49tAvtDrJz2bmNFpotLyyQmwOiApLjeWFkH8EjBsBtpjhzzCIrOHuHR1I1gHChDMMDxfFT2k8dqxkvBpMLZ3zFWyJNl3LYbjgy9BkTIngvpQMSgRMl_VZ2eN_fZWk5wVOHjiUJJ9n4Y8IKQRM731vK_XEaK_BdtNLfC1Gw8hfLrIZpC6152zj6RhPn03gOK7G4RL6S21IfWKrw4kl6rdaPLgmMxlaI"`,
|
||||
want: []string{"0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8smoWqJBpit_3P_ntszmbCH2-dGwpsamwQMbLl7QBa7tlfXH_NtpD1vNTGkacraUMyTM5lfg1AR1DLAxs-pNSpg8NfrHbNSRAIacCpOyqtU05Dg9l5LC7ZYwxT35dQWEK0EExLER-wxjW9DrDZNQV4J3Ktv1Z4ANT2N2rqAjPYqHTDPCCcOi980ptizeImgVYiVr37Ff0Hnr_lAi4Em0wGB7KDdu319sV9Sebe91FIRDs7GVvvv7GFvKjTeXJwHCpbhdqX4X2TRMryNrTNZ8QY7_Wa25MQm7v0qfFqDW_pRMxxohGhClSedZFnkzrreIhZ8ULJ9NCf8YENRHDP3LuOJP5gex-H0MUNsJQLxlDq3bH-i7Fz_cTEB3UN_bvgE9aNe-5gal-ykO_gSx-Kk5D-vZWpLDrFUdRSGYHmKr1zgEZvQjsFUj8pGWgUwssqN9SOPxTYIEzQaxPAul5AFKcxGYt2l4Kvhh58txUdayFAglWrkx1lrxnpIcjoRmHOo45AKlgH30bVOjjltwvD4L9SGMAHhni3F6mCB6aNLGpYCHjrbdsiWolHKV0leJmBYl2Ye4eosQf9YYdgPAbCQKqOJ6gfrxJJTcfrISqDVw1c6C9qPPdHbvdol_KfdJntyfuPpHovx7AfARBcjb6nMgYRBI0wFWsGuTNDcylicMFRcZx6v283wBv4U_0PrG1_Yd5ktfgaTVXF733C-ma_-s49tAvtDrJz2bmNFpotLyyQmwOiApLjeWFkH8EjBsBtpjhzzCIrOHuHR1I1gHChDMMDxfFT2k8dqxkvBpMLZ3zFWyJNl3LYbjgy9BkTIngvpQMSgRMl_VZ2eN_fZWk5wVOHjiUJJ9n4Y8IKQRM731vK_XEaK_BdtNLfC1Gw8hfLrIZpC6152zj6RhPn03gOK7G4RL6S21IfWKrw4kl6rdaPLgmMxlaI"},
|
||||
},
|
||||
{
|
||||
name: "valid - token+client+tenant",
|
||||
input: `
|
||||
{
|
||||
"tokenType": "Bearer",
|
||||
"expiresIn": 4742,
|
||||
"expiresOn": "2024-06-07 09:09:22.294640",
|
||||
"resource": "https://graph.windows.net",
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
||||
"refreshToken": "0.AUUAMe_N-B6jSkuT5F9XHpElWlj2JcxuFFnRLm_3awiSnuJQsa1.AgABAwEAAADnfolhJpSnRYB1SVj-Hgd8Agrf-wUA9P9oElBtlKe8a-5_1t2eEmBef50SCv8exOOrgjUFMLtPQj_XH1rq3Onj2dCFQaHzhm7DfoOxj5LH4kR9jPIbPf2yRI0CgxFLEGMf0biO9LxmvVwb_NKTScIc_MK4eBsXG-En_e3vaIJS5t-ghSvPAKzl3pxiYVvBdP1i_nUHPl4dsCkk9SKCexWnhi4tg9xVVIi-MIkGDJxThmuKfAko1VHMgx-tsHRKgPoXlJi51uNO0KQQUxnDnjiWmLapCe3hVtjfoINBlb3CpiHkfW5G9dzF4cmFOQJQG9RdW-CU6t4VmlamK9gSbNYfyd7fWr7Ebv9Bo06eWEwEBpQmJONJERNScnqMs5Ztba9kUHchXqJd9wZMH-NtWejuR92IqMmPoaY4DP52Yodu2hWZPv0pFEFsthPJ3YpViOaJnCoSQ7ba-qzVr8TnvFlkI8EfFKNbl47_WncwKXDrPk2FlZwG4ywX7s0dXYvXDJ-rMQHsDcJDMABQXrxaU0Z7ozCk_ftVgBQocWZHAkzBtWZNw9dS4ltux0GeAYekUjzE7UYrPw41DLWOLrr7V-kx5sZ6h66iiTi-zdsJ28LnRIX4aZ6IC7jxIG0FK-roPldOEjy0XJ-V6QmyjkEYT3PK23vUTHIz3EQ8JqGNJMJO5mWwbedlIl2xq-0CczybkR2MJgr4UAQKUBFMYuUYGWrVygte9d48usQ6-MhAavmkyZb5Mo_PeMnnNef-cl6c8RUzMAOpeiumFEG-gTzyDgaoM1eFjtYKTz0mr-0lPfrEavE4LfGXh87oDb0lNrbbkMNhAXjz2rJW8ex1REfeBH4oit0WeMWH-sIvpT3H8jsYIawfPp7rBN9z_TMX9AUbqROEY2Nv1jSJsXCX0sjLRweYiQnl-hHFfLcWwFIFjMfs7eOKSiOBKB3ZqjQw_A8OVDxhAQJybiVgW8U41IAjXGX0DNilrmE0PhDAqs5jQIBSO66G05yJj1RY3b2z8cYMG1lKAZ10IIDfo8f3FU-_m-w6zNVVkNZko89bX8tA91EjXpoUvmnPZKT84Qx9KvtRM561ABVEYnE152821Xy0HeObVue6M5WlF0puvqk1HnkfAUDxMk6qO1Xy7o0myTIV1R2yxFPpQX_pwCRB1IutSqz0s6E1XyfbRyv8TKxjX3_tGgvUy8KrZFeYJ9pRFsKIN_AJ9_a2GMG6h1b9aCIaA7jGlOkYlC-4LnhqoKxs4RpJJIpWWN6wZstGmIACwJS4",
|
||||
"familyName": "Doe",
|
||||
"givenName": "John",
|
||||
"identityProvider": "live.com",
|
||||
"tenantId": "16515984-9303-47f6-a59f-917611c8cb2b",
|
||||
"userId": "[email protected]",
|
||||
"isUserIdDisplayable": true,
|
||||
"isMRRT": true,
|
||||
"_clientId": "1b730954-1685-4b74-9bfd-dac224a7b894",
|
||||
"_authority": "https://login.microsoftonline.com/16515984-9303-47f6-a59f-917611c8cb2b"
|
||||
}`,
|
||||
want: []string{`{"refreshToken":"0.AUUAMe_N-B6jSkuT5F9XHpElWlj2JcxuFFnRLm_3awiSnuJQsa1.AgABAwEAAADnfolhJpSnRYB1SVj-Hgd8Agrf-wUA9P9oElBtlKe8a-5_1t2eEmBef50SCv8exOOrgjUFMLtPQj_XH1rq3Onj2dCFQaHzhm7DfoOxj5LH4kR9jPIbPf2yRI0CgxFLEGMf0biO9LxmvVwb_NKTScIc_MK4eBsXG-En_e3vaIJS5t-ghSvPAKzl3pxiYVvBdP1i_nUHPl4dsCkk9SKCexWnhi4tg9xVVIi-MIkGDJxThmuKfAko1VHMgx-tsHRKgPoXlJi51uNO0KQQUxnDnjiWmLapCe3hVtjfoINBlb3CpiHkfW5G9dzF4cmFOQJQG9RdW-CU6t4VmlamK9gSbNYfyd7fWr7Ebv9Bo06eWEwEBpQmJONJERNScnqMs5Ztba9kUHchXqJd9wZMH-NtWejuR92IqMmPoaY4DP52Yodu2hWZPv0pFEFsthPJ3YpViOaJnCoSQ7ba-qzVr8TnvFlkI8EfFKNbl47_WncwKXDrPk2FlZwG4ywX7s0dXYvXDJ-rMQHsDcJDMABQXrxaU0Z7ozCk_ftVgBQocWZHAkzBtWZNw9dS4ltux0GeAYekUjzE7UYrPw41DLWOLrr7V-kx5sZ6h66iiTi-zdsJ28LnRIX4aZ6IC7jxIG0FK-roPldOEjy0XJ-V6QmyjkEYT3PK23vUTHIz3EQ8JqGNJMJO5mWwbedlIl2xq-0CczybkR2MJgr4UAQKUBFMYuUYGWrVygte9d48usQ6-MhAavmkyZb5Mo_PeMnnNef-cl6c8RUzMAOpeiumFEG-gTzyDgaoM1eFjtYKTz0mr-0lPfrEavE4LfGXh87oDb0lNrbbkMNhAXjz2rJW8ex1REfeBH4oit0WeMWH-sIvpT3H8jsYIawfPp7rBN9z_TMX9AUbqROEY2Nv1jSJsXCX0sjLRweYiQnl-hHFfLcWwFIFjMfs7eOKSiOBKB3ZqjQw_A8OVDxhAQJybiVgW8U41IAjXGX0DNilrmE0PhDAqs5jQIBSO66G05yJj1RY3b2z8cYMG1lKAZ10IIDfo8f3FU-_m-w6zNVVkNZko89bX8tA91EjXpoUvmnPZKT84Qx9KvtRM561ABVEYnE152821Xy0HeObVue6M5WlF0puvqk1HnkfAUDxMk6qO1Xy7o0myTIV1R2yxFPpQX_pwCRB1IutSqz0s6E1XyfbRyv8TKxjX3_tGgvUy8KrZFeYJ9pRFsKIN_AJ9_a2GMG6h1b9aCIaA7jGlOkYlC-4LnhqoKxs4RpJJIpWWN6wZstGmIACwJS4","clientId":"1b730954-1685-4b74-9bfd-dac224a7b894","tenantId":"16515984-9303-47f6-a59f-917611c8cb2b"}`},
|
||||
},
|
||||
{
|
||||
name: "valid - 0. in README",
|
||||
input: `
|
||||
### Connection settings
|
||||
|
||||
The connection settings are defined in the automation variables.
|
||||
1. Create the following [user defined variables](https://docs.helloid.com/hc/en-us/articles/360014169933-How-to-Create-and-Manage-User-Defined-Variables)
|
||||
|
||||
| Variable name | Description | Example value |
|
||||
| ----------------- | ------------------------------------------------------------- | ------------------------------------- |
|
||||
| AFASBaseUri | Base URI of the AFAS REST API endpoint for this environment | https://12345.rest.afas.online/ProfitRestServices |
|
||||
| AFASToke | App token in XML format for this environment | \<token>\<version>1\</version>\<data>D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\</data>\</token> |
|
||||
| AADtenantID | Id of the Azure tenant | 12fc345b-0c67-4cde-8902-dabf2cad34b5 |
|
||||
| AADAppId | Id of the Azure app | f12345c6-7890-1f23-b456-789eb0bb1c23 |
|
||||
| AADRefreshToken | Refresh token of the Azure app | 0.ABCDEFGHIJKLMNOPQRS_PK0mtsE5afl5BYdPsASFbrS7jIZ0AAc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P-XOTtPMo2xp9vfbHGvVkHaBZh4D3YmTkx_WagBOk358QjDwHUsiuVvyKvP6FTbQQt8kCidfMC9cmIYesHG4Ft2B1HwJNX28OpiFPuFti1D4Is30GgQ685i_ovS4iXDCUgtm2zpI6ZQJVqoOidXZQW_lSupdcclMK_JCIb7LBuJBDXfy0-f75C734_nxL0nggS9mn-e_KuJpHvypvU8OS9MPDBArhUopZum2y-2oNE65Wr-xpKm_Zeyr3iUGSZg98nbaryHw-lbeyFC8LcNqqMB_T7BcgvJicHSnj6DtjjpMyjKMwsCAnxz2bUYoLLjGFHk8EhDUCuV9lzUW1BTko5_I31TQdX0XY94vHTU34N93t3QPrQFMf8UhDjfQKiCDj3r2b7YR9ndS8MNp9MIa1CbL8vI4EM8GO4wtVI30Dhca4HaMtpph6uJp3echt-q7AVNQ_7ZHgx_YFZNqDmJyYq3nrae7LYRo0kvM382ss7JpCylodwya89mC_SlnrFhLM_zbt1TQkOtZqiVHbdQk3z-MX1iZso5Mk17Yks1ao0mS0RJfWVWSlOq_Sp-2yaiCsP-lV1PVdvvY_AkuOulP1kPG_VfC0DN3pGjSQJ8J9Ot5hfyElWyPst9Nc-ODErLhEqIl-3IR6wPKFN2ffjt8-dtCVMlVdBd1QANQOFBiIGA-_BZdGLvzROrWCOE9dDtyBQ_LnxdnnOVdjUqJ-xdql1p13Xjy6ZTtcZtTDmFN5hSMffYuUtuwEOy_Xb91Y2tvwOxcSe9dj7ElOLZDo2C7fGsMgaIJ1gK8xt9OWsS1o1sQZKQADTZq5TTxJp7PY3tJsUnOlD4q8ZEyVBQAvRKinpajBRcbq2lTCVt0JgXAryWztqYTpAxiqaBr51vuR4pbVRtKv-h_10tYD-TUV1WeX2fY3GuZA4B5g |
|
||||
|
||||
## contents`,
|
||||
want: []string{`{"refreshToken":"0.ABCDEFGHIJKLMNOPQRS_PK0mtsE5afl5BYdPsASFbrS7jIZ0AAc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P-XOTtPMo2xp9vfbHGvVkHaBZh4D3YmTkx_WagBOk358QjDwHUsiuVvyKvP6FTbQQt8kCidfMC9cmIYesHG4Ft2B1HwJNX28OpiFPuFti1D4Is30GgQ685i_ovS4iXDCUgtm2zpI6ZQJVqoOidXZQW_lSupdcclMK_JCIb7LBuJBDXfy0-f75C734_nxL0nggS9mn-e_KuJpHvypvU8OS9MPDBArhUopZum2y-2oNE65Wr-xpKm_Zeyr3iUGSZg98nbaryHw-lbeyFC8LcNqqMB_T7BcgvJicHSnj6DtjjpMyjKMwsCAnxz2bUYoLLjGFHk8EhDUCuV9lzUW1BTko5_I31TQdX0XY94vHTU34N93t3QPrQFMf8UhDjfQKiCDj3r2b7YR9ndS8MNp9MIa1CbL8vI4EM8GO4wtVI30Dhca4HaMtpph6uJp3echt-q7AVNQ_7ZHgx_YFZNqDmJyYq3nrae7LYRo0kvM382ss7JpCylodwya89mC_SlnrFhLM_zbt1TQkOtZqiVHbdQk3z-MX1iZso5Mk17Yks1ao0mS0RJfWVWSlOq_Sp-2yaiCsP-lV1PVdvvY_AkuOulP1kPG_VfC0DN3pGjSQJ8J9Ot5hfyElWyPst9Nc-ODErLhEqIl-3IR6wPKFN2ffjt8-dtCVMlVdBd1QANQOFBiIGA-_BZdGLvzROrWCOE9dDtyBQ_LnxdnnOVdjUqJ-xdql1p13Xjy6ZTtcZtTDmFN5hSMffYuUtuwEOy_Xb91Y2tvwOxcSe9dj7ElOLZDo2C7fGsMgaIJ1gK8xt9OWsS1o1sQZKQADTZq5TTxJp7PY3tJsUnOlD4q8ZEyVBQAvRKinpajBRcbq2lTCVt0JgXAryWztqYTpAxiqaBr51vuR4pbVRtKv-h_10tYD-TUV1WeX2fY3GuZA4B5g","clientId":"f12345c6-7890-1f23-b456-789eb0bb1c23","tenantId":"12fc345b-0c67-4cde-8902-dabf2cad34b5"}`},
|
||||
},
|
||||
|
||||
// Valid 1.
|
||||
{
|
||||
name: "valid - 1. token only",
|
||||
input: ` "refresh_token": "1.AVEAPn9m_nUaQ0iPPuqFsWAkYjIyPGgTIDFBgPvLEoUSLQVRAG5RAA.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDj_wUA9P8ZsxEzkXInsWHkCylMQMSSKto-NoegPmNj0uIemgAvxjnsDVGpC7sDRl4oEd51nQLQYowQYQ8aEcHh3nRrACc37UPYN-bwDte-tiwOEKuuGTOUrZft6YCqYiBoj7p3GZvKkIkUOGZvx7nydI1WoH9c7Z62NstZJ7ju_V38t5He6cKXEzNtlnrHpctxJX1uxxizdvwIR-_2VyMQjSSJS5lOS0Hi4Z_Nlthos5G-Gb-h9Y96fkkVm0D5E4xQh9avS7eCAPE2-N_guF3tmm7B4aqJg1lGnwv3WDWim14QhkF6Aji7juJUNmAExFyBaM7WnV_u3JnT-UNCz1p0O3AHa9d-dyDTUxQ8m_riB1HPoZZo6wPxg6txs6-fUE4LDR6tB5b43zwUl9XufcL4gKwnheLr8LvpJGjJn2tZUQzoU-ow4AZtJIxblfgYU_Zq0WOPJXltgAEw2JVoGsRy2jX8mXFZq1iCK5uEKBPXgrEfV-simUqI8GRZgXA1EnxG950MuaVfP3ZpsTYPGsvQgSzsUBKSy7cLd0p7UYtLub9UpX2PJxHrLQjACF-CSOMatVfSNzTErhSEmVWndpt87Yhova-XJUV48UxQ4ZZz26G6nOQ9qJ6db8ReAzBnok10e0eBuHR6K0OzcO54gjiQWPR4Tur7hD82KmYdOtShz234hDRGuS_b7mThfr_2ef9b2TQ9XYEV2QDUWiFYplfU0kOKA-wA7jOJGhXDkaJCIURxy53KuZPolXjTAy4",
|
||||
"expires_at": 1733138350.558087
|
||||
}`,
|
||||
want: []string{"1.AVEAPn9m_nUaQ0iPPuqFsWAkYjIyPGgTIDFBgPvLEoUSLQVRAG5RAA.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDj_wUA9P8ZsxEzkXInsWHkCylMQMSSKto-NoegPmNj0uIemgAvxjnsDVGpC7sDRl4oEd51nQLQYowQYQ8aEcHh3nRrACc37UPYN-bwDte-tiwOEKuuGTOUrZft6YCqYiBoj7p3GZvKkIkUOGZvx7nydI1WoH9c7Z62NstZJ7ju_V38t5He6cKXEzNtlnrHpctxJX1uxxizdvwIR-_2VyMQjSSJS5lOS0Hi4Z_Nlthos5G-Gb-h9Y96fkkVm0D5E4xQh9avS7eCAPE2-N_guF3tmm7B4aqJg1lGnwv3WDWim14QhkF6Aji7juJUNmAExFyBaM7WnV_u3JnT-UNCz1p0O3AHa9d-dyDTUxQ8m_riB1HPoZZo6wPxg6txs6-fUE4LDR6tB5b43zwUl9XufcL4gKwnheLr8LvpJGjJn2tZUQzoU-ow4AZtJIxblfgYU_Zq0WOPJXltgAEw2JVoGsRy2jX8mXFZq1iCK5uEKBPXgrEfV-simUqI8GRZgXA1EnxG950MuaVfP3ZpsTYPGsvQgSzsUBKSy7cLd0p7UYtLub9UpX2PJxHrLQjACF-CSOMatVfSNzTErhSEmVWndpt87Yhova-XJUV48UxQ4ZZz26G6nOQ9qJ6db8ReAzBnok10e0eBuHR6K0OzcO54gjiQWPR4Tur7hD82KmYdOtShz234hDRGuS_b7mThfr_2ef9b2TQ9XYEV2QDUWiFYplfU0kOKA-wA7jOJGhXDkaJCIURxy53KuZPolXjTAy4"},
|
||||
},
|
||||
{
|
||||
name: "valid - 1. tenant+client+token",
|
||||
input: `
|
||||
async function getAccessToken() {
|
||||
const tenantId = "31d1b7f4-4c4c-44cf-8d4e-b63e8512543e";
|
||||
const clientId = "16ed71fb-067e-47d9-b4bc-7656b14f1c5e";
|
||||
const clientSecret = ""; //para que funcione en sus ambientes tienen que poner el secreto,
|
||||
//si no lo tienen me lo piden y se los comparto por whatsapp,
|
||||
//lo tuvé que quitar porque no me dejaba hacer commit de los cambios en el repositorio
|
||||
const scope = "https://analysis.windows.net/powerbi/api/.default";
|
||||
let refresh_token = "1.AWEBqY9dsQppikubCN8WQsbFVyBfrV_ioNtAn7uoXAmQmkRiAUthAQ.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDs_yUA9P8OThUk9d3XIZbW4OGsJwHqqvjnVfcvEH4nejPU6R6-3onU34aSbVTEmxec0Nn3PaKfTBxucT-bu5XLSaTZSePKAAZw22RpqBb1w6ySb5GvvcCVpFU45mNfX5OH63y2Ryt-B7Beyp5yzlIgVgQA2S4OKhd_2qoVQoQXLApTwR78awwMFEQ7eVSbu5DO52dxisjB9ApHmpDCBip5y2MzyS7TizR31e-qBTnCMWt9RuHcKJySFFa-yPRBqYCgZLQWmEsKXBq-RIJToFsaGhVH2sXGXec0-Qsd9CvSPNFfGUDb_d2FLkZyKYKPra7Wmsvpw6qZJxO_TYprs1TbeWJYTTWT6WWI3xn10XtVml0a0P77ESqAWs-nbl6fS15mE24ZVU6rsuD7Q5AmtFfaddVN-JFP3fJ-6VsiY3KAefmdNULF_AVfMxAelBDSHtNllsMv4Qqs8N4h5bY4cabHibpu_OVA7WzfkNbxQ1dZpccZ9pi--xq5BCU3QAzereqYwmKretykB8twHw8Ryl5UVGocBNSJD65w2K3FJGZ6zbinfb_g1vV39iFxLdUz3JT1obce5ndeMBUeFmhN5XsczKAzTRK9c8aX6sdOd5pw5vUe-98qFRypPvCSF4hVA2ziwH38V9Dtc56UEVSMKISOacRMs8F_m9XtxP4X5KsWICIrK8_EXWfgmvEQnXm5PHV24ROsbnmmtUJWN1-vgzmNmSQ54_66W-fsCdnYAzDlwZeKr7wTZYO82nepNHX-wvTTEPV-QlrTPQFAlguP6nnxRc8MoxyiEvT4fOsDwD4yWFkLMMlKbyB6pQF_0CW_rQbyl0e6EKP2HbIDVKj628MDizjsdX693gplJevjF5g";
|
||||
`,
|
||||
want: []string{`{"refreshToken":"1.AWEBqY9dsQppikubCN8WQsbFVyBfrV_ioNtAn7uoXAmQmkRiAUthAQ.AgABAwEAAADW6jl31mB3T7ugrWTT8pFeAwDs_yUA9P8OThUk9d3XIZbW4OGsJwHqqvjnVfcvEH4nejPU6R6-3onU34aSbVTEmxec0Nn3PaKfTBxucT-bu5XLSaTZSePKAAZw22RpqBb1w6ySb5GvvcCVpFU45mNfX5OH63y2Ryt-B7Beyp5yzlIgVgQA2S4OKhd_2qoVQoQXLApTwR78awwMFEQ7eVSbu5DO52dxisjB9ApHmpDCBip5y2MzyS7TizR31e-qBTnCMWt9RuHcKJySFFa-yPRBqYCgZLQWmEsKXBq-RIJToFsaGhVH2sXGXec0-Qsd9CvSPNFfGUDb_d2FLkZyKYKPra7Wmsvpw6qZJxO_TYprs1TbeWJYTTWT6WWI3xn10XtVml0a0P77ESqAWs-nbl6fS15mE24ZVU6rsuD7Q5AmtFfaddVN-JFP3fJ-6VsiY3KAefmdNULF_AVfMxAelBDSHtNllsMv4Qqs8N4h5bY4cabHibpu_OVA7WzfkNbxQ1dZpccZ9pi--xq5BCU3QAzereqYwmKretykB8twHw8Ryl5UVGocBNSJD65w2K3FJGZ6zbinfb_g1vV39iFxLdUz3JT1obce5ndeMBUeFmhN5XsczKAzTRK9c8aX6sdOd5pw5vUe-98qFRypPvCSF4hVA2ziwH38V9Dtc56UEVSMKISOacRMs8F_m9XtxP4X5KsWICIrK8_EXWfgmvEQnXm5PHV24ROsbnmmtUJWN1-vgzmNmSQ54_66W-fsCdnYAzDlwZeKr7wTZYO82nepNHX-wvTTEPV-QlrTPQFAlguP6nnxRc8MoxyiEvT4fOsDwD4yWFkLMMlKbyB6pQF_0CW_rQbyl0e6EKP2HbIDVKj628MDizjsdX693gplJevjF5g","clientId":"16ed71fb-067e-47d9-b4bc-7656b14f1c5e","tenantId":"31d1b7f4-4c4c-44cf-8d4e-b63e8512543e"}`},
|
||||
},
|
||||
{
|
||||
name: "valid - 1. with more than 3 segments",
|
||||
input: `- request:
|
||||
body: client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46&grant_type=refresh_token&client_info=1&claims=%7B%22access_token%22%3A+%7B%22xms_cc%22%3A+%7B%22values%22%3A+%5B%22CP1%22%5D%7D%7D%7D&refresh_token=1.AAEA-W8xnNOnEke-ljgE71hsE5V3sATbjRpGu-4G-eG_e0YBAFIaAA.1.AgABAwEAAAAuQLDzsjJ3TYwhxABdnzRyAwDs_wUA7_9ENX2x1IM0b4hPzM-Ba_-qsHQqGxLKdo8wXF8BKQjnNc3wrqvP54z75uPEWb9uNOqw_Y8oxEQHggfkdIiq1NjPeA-A9jR2AI28nwlPd8dyuglTrUhLEKCKH0UFCeOi0lSxr7pefIa97LSJsDFKYPg1bCd9iuyRI5zQVGFbfHfq7gI8TSbpaVRSzNlsgftBrzIH_Zk55WCWz9ln8B-K1mc8gFDKsnclyvyCQU6e4CE0_6dHq1FXD-BwwV0yC1S9yyh673EHgY47s950p3Yqc7a8fOKY7iuwNKCDML51CUAZusRWfRYx0d1FXMI-JUfoBHTaZwsQyFePTlLjxkk2iEk4v9PTlTIvBdzZ6A8BVNDvpK_lBHgEpN_HVEbWM9ZHvWbeIU2_Lwt0SqLJEnq5GkTowX3aJe36JXWE6NBp5NJWS5-0EfEtl5iIWxtNG6u2E7lGAEbvUEAGXYa0abLxNwRiKvMNCKw01v42xIw1HqonNMT-tgY08KI3Icbyv-hzEwUwY8LYcjOGQTejDRe7CM9IogLe5flpK6m5aYKF8k4qVMN2PqCGCpofcqqyS448k9ATYx1Dm4-MAVsWScb22M106yIRSIbdo7tKdr3vBdNf0_FT0I-r20iDnUw_6sQc_Q8tR9uRuZbtrwD6IBAyYzqTG2KacAG6Gac-J5p-fsnPdjy0RmurvE149oA4G0KcAatNPmreiGzArXJEx7z20QwCgrh4j11j3dLJQMMafaxPdjHjPkwrG8Vz7xHVvRlfcn6x1d2Xhyq2VB6BwdZVIukbvxSg9Ci34qlKunOtohUxvisRRryV-w6MV1BomJz3W0QM0cTm5KVWpH9_0tQrioelqwvstQ6bOHRA3r7CzTZw0lfGMoDlaPubUqiy5t6P_b40hpkt40drKKHN972GwSDeR19cYiUFIONkc5APsV17tq0XZZgB8zpL-WilYK2SBQzescd4W1yXpFuh-uZ7bLAnQaa6xZzFDkN9-v4chZ2UAAvBsIURr7Q_8N_w2nH_.AQABFAEAAAAuQLDzsjJ3TYwhxABdnzRyNxO45BG1O4-twAhtMj2ZAGVMkIFTMaFoxpzzBJ7zB99xWtRIkmYAput3pQWfY44PP3WY0mRvEqSuLWlLa79Nz8jJANXNNTbPvXt8F_BDxeZUwb7gNax-q2Fr12Gb5YnTVnq9EUU9QEcuThPgC7tFWFu3_iwKjR-IMMcnQj6C7eh-ZcPMIn5Pkb3FkLwD7aZblol-4Z18pXV7dBOO8i0i4VZ5ud7tkxL5UjDZdbM8NrogAA&scope=https%3A%2F%2Fmanagement.core.windows.net%2F%2F.default+offline_access+openid+profile
|
||||
headers:`,
|
||||
want: []string{`1.AAEA-W8xnNOnEke-ljgE71hsE5V3sATbjRpGu-4G-eG_e0YBAFIaAA.1.AgABAwEAAAAuQLDzsjJ3TYwhxABdnzRyAwDs_wUA7_9ENX2x1IM0b4hPzM-Ba_-qsHQqGxLKdo8wXF8BKQjnNc3wrqvP54z75uPEWb9uNOqw_Y8oxEQHggfkdIiq1NjPeA-A9jR2AI28nwlPd8dyuglTrUhLEKCKH0UFCeOi0lSxr7pefIa97LSJsDFKYPg1bCd9iuyRI5zQVGFbfHfq7gI8TSbpaVRSzNlsgftBrzIH_Zk55WCWz9ln8B-K1mc8gFDKsnclyvyCQU6e4CE0_6dHq1FXD-BwwV0yC1S9yyh673EHgY47s950p3Yqc7a8fOKY7iuwNKCDML51CUAZusRWfRYx0d1FXMI-JUfoBHTaZwsQyFePTlLjxkk2iEk4v9PTlTIvBdzZ6A8BVNDvpK_lBHgEpN_HVEbWM9ZHvWbeIU2_Lwt0SqLJEnq5GkTowX3aJe36JXWE6NBp5NJWS5-0EfEtl5iIWxtNG6u2E7lGAEbvUEAGXYa0abLxNwRiKvMNCKw01v42xIw1HqonNMT-tgY08KI3Icbyv-hzEwUwY8LYcjOGQTejDRe7CM9IogLe5flpK6m5aYKF8k4qVMN2PqCGCpofcqqyS448k9ATYx1Dm4-MAVsWScb22M106yIRSIbdo7tKdr3vBdNf0_FT0I-r20iDnUw_6sQc_Q8tR9uRuZbtrwD6IBAyYzqTG2KacAG6Gac-J5p-fsnPdjy0RmurvE149oA4G0KcAatNPmreiGzArXJEx7z20QwCgrh4j11j3dLJQMMafaxPdjHjPkwrG8Vz7xHVvRlfcn6x1d2Xhyq2VB6BwdZVIukbvxSg9Ci34qlKunOtohUxvisRRryV-w6MV1BomJz3W0QM0cTm5KVWpH9_0tQrioelqwvstQ6bOHRA3r7CzTZw0lfGMoDlaPubUqiy5t6P_b40hpkt40drKKHN972GwSDeR19cYiUFIONkc5APsV17tq0XZZgB8zpL-WilYK2SBQzescd4W1yXpFuh-uZ7bLAnQaa6xZzFDkN9-v4chZ2UAAvBsIURr7Q_8N_w2nH_.AQABFAEAAAAuQLDzsjJ3TYwhxABdnzRyNxO45BG1O4-twAhtMj2ZAGVMkIFTMaFoxpzzBJ7zB99xWtRIkmYAput3pQWfY44PP3WY0mRvEqSuLWlLa79Nz8jJANXNNTbPvXt8F_BDxeZUwb7gNax-q2Fr12Gb5YnTVnq9EUU9QEcuThPgC7tFWFu3_iwKjR-IMMcnQj6C7eh-ZcPMIn5Pkb3FkLwD7aZblol-4Z18pXV7dBOO8i0i4VZ5ud7tkxL5UjDZdbM8NrogAA`},
|
||||
},
|
||||
|
||||
// Invalid
|
||||
{
|
||||
name: "invalid - too short",
|
||||
input: `"refresh_token": "0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8sm..."`,
|
||||
},
|
||||
{
|
||||
name: "invalid - low entropy",
|
||||
input: `"refresh_token": "0.Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.Agxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"`,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,8 +65,9 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aylien"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ayrshare"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_batch"
|
||||
azure_serviceprincipal_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v1"
|
||||
azure_serviceprincipal_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2"
|
||||
azure_entra_refreshtoken "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/refreshtoken"
|
||||
azure_entra_serviceprincipal_v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v1"
|
||||
azure_entra_serviceprincipal_v2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_openai"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_storage"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azurecontainerregistry"
|
||||
@@ -891,8 +892,9 @@ func buildDetectorList() []detectors.Detector {
|
||||
&axonaut.Scanner{},
|
||||
&aylien.Scanner{},
|
||||
&ayrshare.Scanner{},
|
||||
&azure_serviceprincipal_v1.Scanner{},
|
||||
&azure_serviceprincipal_v2.Scanner{},
|
||||
&azure_entra_refreshtoken.Scanner{},
|
||||
&azure_entra_serviceprincipal_v1.Scanner{},
|
||||
&azure_entra_serviceprincipal_v2.Scanner{},
|
||||
&azure_batch.Scanner{},
|
||||
&azurecontainerregistry.Scanner{},
|
||||
&azuredevopspersonalaccesstoken.Scanner{},
|
||||
|
||||
@@ -1117,6 +1117,7 @@ const (
|
||||
DetectorType_TwitchAccessToken DetectorType = 1010
|
||||
DetectorType_TwilioApiKey DetectorType = 1011
|
||||
DetectorType_Sanity DetectorType = 1012
|
||||
DetectorType_AzureRefreshToken DetectorType = 1013
|
||||
)
|
||||
|
||||
// Enum value maps for DetectorType.
|
||||
@@ -2131,6 +2132,7 @@ var (
|
||||
1010: "TwitchAccessToken",
|
||||
1011: "TwilioApiKey",
|
||||
1012: "Sanity",
|
||||
1013: "AzureRefreshToken",
|
||||
}
|
||||
DetectorType_value = map[string]int32{
|
||||
"Alibaba": 0,
|
||||
@@ -3142,6 +3144,7 @@ var (
|
||||
"TwitchAccessToken": 1010,
|
||||
"TwilioApiKey": 1011,
|
||||
"Sanity": 1012,
|
||||
"AzureRefreshToken": 1013,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3595,7 +3598,7 @@ var file_detectors_proto_rawDesc = []byte{
|
||||
0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34,
|
||||
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45,
|
||||
0x10, 0x04, 0x2a, 0xbc, 0x81, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x10, 0x04, 0x2a, 0xd4, 0x81, 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, 0x03, 0x12,
|
||||
@@ -4631,11 +4634,13 @@ var file_detectors_proto_rawDesc = []byte{
|
||||
0x69, 0x74, 0x63, 0x68, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10,
|
||||
0xf2, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x77, 0x69, 0x6c, 0x69, 0x6f, 0x41, 0x70, 0x69, 0x4b,
|
||||
0x65, 0x79, 0x10, 0xf3, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x10,
|
||||
0xf4, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x73, 0x70,
|
||||
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0xf4, 0x07, 0x12, 0x16, 0x0a, 0x11, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65,
|
||||
0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xf5, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -1022,6 +1022,7 @@ enum DetectorType {
|
||||
TwitchAccessToken = 1010;
|
||||
TwilioApiKey = 1011;
|
||||
Sanity = 1012;
|
||||
AzureRefreshToken = 1013;
|
||||
}
|
||||
|
||||
message Result {
|
||||
|
||||
Reference in New Issue
Block a user