From 55cfe6b18a2b3d8bde6a04720c735674211247bc Mon Sep 17 00:00:00 2001 From: SyedAliHamad Date: Fri, 11 Jul 2025 11:15:51 +0500 Subject: [PATCH] add detection for updated asana personal access token (#4273) * add detection for updated asana personal access token * removed unnecessary checks * add integration tests * add verification error --- .../asanapersonalaccesstoken.go | 5 +- ...anapersonalaccesstoken_integration_test.go | 81 ++++++++++++++----- .../asanapersonalaccesstoken_test.go | 16 +++- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go index abd1ed75e..7e3eb830d 100644 --- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go +++ b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go @@ -22,7 +22,10 @@ var _ detectors.Detector = (*Scanner)(nil) var ( client = common.SaneHttpClient() - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"asana"}) + `\b([0-9]{1,}\/[0-9]{16,}:[A-Za-z0-9]{32,})\b`) + // Updated pattern to handle both old and new token formats + // Old format: [digits]/[16+ digits]:[32+ chars] + // New format: [digits]/[16+ digits]/[16+ digits]:[32+ chars] + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"asana"}) + `\b([0-9]{1,}\/[0-9]{16,}(?:\/[0-9]{16,})?:[A-Za-z0-9]{32,})\b`) ) // Keywords are used for efficiently pre-filtering chunks. diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go index 97a61d918..a33eefeb2 100644 --- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go +++ b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go @@ -23,8 +23,15 @@ func TestAsanaPersonalAccessToken_FromChunk(t *testing.T) { if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - secret := testSecrets.MustGetField("ASANA_PAT") - inactiveSecret := testSecrets.MustGetField("ASANA_PAT_INACTIVE") + testNewSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + + oldFormatSecret := testSecrets.MustGetField("ASANA_PAT") + newFormatSecret := testNewSecrets.MustGetField("ASANA_PAT_NEW") + inactiveOldFormatSecret := testSecrets.MustGetField("ASANA_PAT_INACTIVE") + inactiveNewFormatSecret := testNewSecrets.MustGetField("ASANA_PAT_NEW_INACTIVE") type args struct { ctx context.Context @@ -43,7 +50,7 @@ func TestAsanaPersonalAccessToken_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a asana secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a asana secret %s within", oldFormatSecret)), verify: true, }, want: []detectors.Result{ @@ -71,6 +78,38 @@ func TestAsanaPersonalAccessToken_FromChunk(t *testing.T) { wantErr: false, }, { + name: "found, verified - new format", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a asana secret %s within", newFormatSecret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken, + Verified: true, + }, + }, + wantErr: false, + }, + { + name: "found, unverified - new format", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a asana secret %s but unverified", inactiveNewFormatSecret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken, + Verified: false, + }, + }, + wantErr: false, + }, + { name: "not found", s: Scanner{}, args: args{ @@ -81,25 +120,25 @@ func TestAsanaPersonalAccessToken_FromChunk(t *testing.T) { want: nil, wantErr: false, }, - } - 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) - if (err != nil) != tt.wantErr { - t.Errorf("AsanaPersonalAccessToken.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]) + 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) + if (err != nil) != tt.wantErr { + t.Errorf("AsanaPersonalAccessToken.FromData() error = %v, wantErr %v", err, tt.wantErr) + return } - got[i].Raw = nil - } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("AsanaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) - } - }) + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + got[i].Raw = nil + } + if diff := pretty.Compare(got, tt.want); diff != "" { + t.Errorf("AsanaPersonalAccessToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } } } diff --git a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go index 8cbf64c95..cf8fde9f3 100644 --- a/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go +++ b/pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go @@ -11,8 +11,11 @@ import ( ) var ( - validPattern = "asana_token: 594776654034514343561917591881414702593902454625364993/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn" - invalidPattern = "asana_token: 1724908107002616220416212965%594776654034514343561917591881414702593902454625364993:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn-ij2gwtg/xn9vh4jvsokdfaic0bn" + // Old format token + validPatternOld = "asana_token: 594776654034514343561917591881414702593902454625364993/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn" + // New format token with two forward slashes + newValidPattern = "asana_token: 7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88" + invalidPattern = "asana_token: 1724908107002616220416212965%594776654034514343561917591881414702593902454625364993:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn-ij2gwtg/xn9vh4jvsokdfaic0bn" ) func TestAsanaPersonalAccessToken_Pattern(t *testing.T) { @@ -25,10 +28,15 @@ func TestAsanaPersonalAccessToken_Pattern(t *testing.T) { want []string }{ { - name: "valid pattern", - input: validPattern, + name: "valid pattern - old format", + input: validPatternOld, want: []string{"594776654034514343561917591881414702593902454625364993/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn"}, }, + { + name: "valid pattern - new format", + input: newValidPattern, + want: []string{"7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88"}, + }, { name: "invalid pattern", input: invalidPattern,