diff --git a/pkg/detectors/jiradatacenterpat/jiradatacenterpat.go b/pkg/detectors/jiradatacenterpat/jiradatacenterpat.go new file mode 100644 index 000000000000..ceca36da9355 --- /dev/null +++ b/pkg/detectors/jiradatacenterpat/jiradatacenterpat.go @@ -0,0 +1,151 @@ +package jiradatacenterpat + +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 + detectors.EndpointSetter +} + +// Ensure the Scanner satisfies the interfaces at compile time. +var ( + _ detectors.Detector = (*Scanner)(nil) + _ detectors.EndpointCustomizer = (*Scanner)(nil) +) + +var ( + defaultClient = common.SaneHttpClient() + + // PATs are base64-encoded strings of the form <12-digit-id>:<20-random-bytes> (33 bytes, 44 chars, no padding). + // Since the first byte is always an ASCII digit (0x30–0x39), the first base64 character is always M, N, or O. + // This is also verified by generating 25+ tokens. + // The trailing boundary (?:[^A-Za-z0-9+/=]|\z) is used instead of \b to correctly handle tokens ending in + or /. + patPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira", "atlassian"}) + `\b([MNO][A-Za-z0-9+/]{43})(?:[^A-Za-z0-9+/=]|\z)`) + urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira", "atlassian"}) + `(https?://[A-Za-z0-9][A-Za-z0-9.\-]*(?::\d{1,5})?)`) +) + +func (s Scanner) getClient() *http.Client { + if s.client != nil { + return s.client + } + return defaultClient +} + +// Keywords are used for efficiently pre-filtering chunks. +func (s Scanner) Keywords() []string { + return []string{"jira", "atlassian"} +} + +// FromData will find and optionally verify Jira Data Center PAT 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) + + var tokens []string + for _, match := range patPat.FindAllStringSubmatch(dataStr, -1) { + tokens = append(tokens, match[1]) + } + + foundURLs := make(map[string]struct{}) + for _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) { + foundURLs[match[1]] = struct{}{} + } + uniqueURLs := make([]string, 0, len(foundURLs)) + for url := range foundURLs { + uniqueURLs = append(uniqueURLs, url) + } + + for _, token := range tokens { + for _, endpoint := range s.Endpoints(uniqueURLs...) { + s1 := detectors.Result{ + DetectorType: detector_typepb.DetectorType_JiraDataCenterPAT, + Raw: []byte(token), + RawV2: []byte(token + endpoint), + Redacted: token[:3] + "..." + token[len(token)-3:], + } + + if verify { + isVerified, extraData, verificationErr := verifyPAT(ctx, s.getClient(), endpoint, token) + s1.Verified = isVerified + s1.ExtraData = extraData + s1.SetVerificationError(verificationErr, token) + } + + results = append(results, s1) + + if s1.Verified { + break + } + } + } + + return results, nil +} + +// verifyPAT checks whether the token is valid by calling the /rest/api/2/myself endpoint, +// which returns the currently authenticated user. +// Docs: https://developer.atlassian.com/server/jira/platform/rest/v10002/api-group-myself/#api-api-2-myself-get +func verifyPAT(ctx context.Context, client *http.Client, baseURL, token string) (bool, map[string]string, error) { + u, err := detectors.ParseURLAndStripPathAndParams(baseURL) + if err != nil { + return false, nil, err + } + u.Path = "/rest/api/2/myself" + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody) + if err != nil { + return false, nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp, err := client.Do(req) + if err != nil { + return false, nil, err + } + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + switch resp.StatusCode { + case http.StatusOK: + var result map[string]any + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + // 200 confirms the token is valid; failing to decode only means we can't extract extra data. + return true, nil, nil + } + extraData := map[string]string{} + if name, ok := result["displayName"].(string); ok { + extraData["display_name"] = name + } + if email, ok := result["emailAddress"].(string); ok { + extraData["email_address"] = email + } + return true, extraData, nil + case http.StatusUnauthorized: + return false, nil, nil + default: + return false, nil, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode) + } +} + +func (s Scanner) Type() detector_typepb.DetectorType { + return detector_typepb.DetectorType_JiraDataCenterPAT +} + +func (s Scanner) Description() string { + return "Jira Data Center is a self-hosted version of Jira. Personal Access Tokens (PATs) are used to authenticate API requests to Jira Data Center instances." +} diff --git a/pkg/detectors/jiradatacenterpat/jiradatacenterpat_test.go b/pkg/detectors/jiradatacenterpat/jiradatacenterpat_test.go new file mode 100644 index 000000000000..88120286235c --- /dev/null +++ b/pkg/detectors/jiradatacenterpat/jiradatacenterpat_test.go @@ -0,0 +1,266 @@ +package jiradatacenterpat + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/h2non/gock.v1" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb" +) + +const ( + testToken = "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M" + testEndpoint = "http://jira.example.com" +) + +func TestJiraDataCenterPAT_Pattern(t *testing.T) { + d := Scanner{} + _ = d.SetConfiguredEndpoints("https://jira.example.com") + d.UseFoundEndpoints(true) + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + + tests := []struct { + name string + input string + want []string + }{ + { + name: "valid PAT", + input: `jira_token: NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M`, + want: []string{"NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6Mhttps://jira.example.com"}, + }, + { + name: "URL found near jira keyword", + input: `# jira server: http://jira.internal:8080` + "\n" + `jira token: NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M`, + want: []string{ + "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6Mhttp://jira.internal:8080", + "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6Mhttps://jira.example.com", + }, + }, + { + name: "URL found near atlassian keyword", + input: `# atlassian server: http://jira.internal:8080` + "\n" + `atlassian token: NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M`, + want: []string{ + "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6Mhttp://jira.internal:8080", + "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6Mhttps://jira.example.com", + }, + }, + { + name: "valid PAT ending with +", + input: `jira_token: MzE3MjgzNDMyNTczOmTaXorACdDy8aVJU6FotdRcz2y+`, + want: []string{"MzE3MjgzNDMyNTczOmTaXorACdDy8aVJU6FotdRcz2y+https://jira.example.com"}, + }, + { + name: "not a match - invalid first character", + input: `jira_token: ATg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M`, + want: []string{}, + }, + { + name: "not a match - substring of longer base64 string", + input: `jira_token: MzE3MjgzNDMyNTczOmTaXorACdDy8aVJU6FotdRcz2y+AAAA`, + want: []string{}, + }, + { + name: "not a match - followed by base64 padding", + input: `jira_token: NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M=`, + want: []string{}, + }, + { + name: "too short - not a match", + input: `jira_token: NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0`, + want: []string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input)) + if len(test.want) > 0 && 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) + } + }) + } +} + +func TestJiraDataCenterPAT_FromData(t *testing.T) { + client := common.SaneHttpClient() + + d := Scanner{client: client} + _ = d.SetConfiguredEndpoints(testEndpoint) + d.UseFoundEndpoints(false) + + defer gock.Off() + defer gock.RestoreClient(client) + gock.InterceptClient(client) + + tests := []struct { + name string + setup func() + data string + verify bool + wantResults int + wantVerified bool + wantVerificationErr bool + wantExtraData map[string]string + }{ + { + name: "found, verified", + setup: func() { + gock.New(testEndpoint). + Get("/rest/api/2/myself"). + MatchHeader("Authorization", fmt.Sprintf("Bearer %s", testToken)). + Reply(http.StatusOK). + JSON(map[string]any{ + "displayName": "Test User", + "emailAddress": "test@example.com", + }) + }, + data: fmt.Sprintf("jira token: %s", testToken), + verify: true, + wantResults: 1, + wantVerified: true, + wantExtraData: map[string]string{ + "display_name": "Test User", + "email_address": "test@example.com", + }, + }, + { + name: "found, verified - invalid json body", + setup: func() { + gock.New(testEndpoint). + Get("/rest/api/2/myself"). + Reply(http.StatusOK). + BodyString("not json") + }, + data: fmt.Sprintf("jira token: %s", testToken), + verify: true, + wantResults: 1, + wantVerified: true, + }, + { + name: "found, unverified (401)", + setup: func() { + gock.New(testEndpoint). + Get("/rest/api/2/myself"). + Reply(http.StatusUnauthorized) + }, + data: fmt.Sprintf("jira token: %s", testToken), + verify: true, + wantResults: 1, + wantVerified: false, + }, + { + name: "not found", + setup: func() {}, + data: "jira config: nothing here", + verify: true, + wantResults: 0, + }, + { + name: "found, verification error on unexpected status", + setup: func() { + gock.New(testEndpoint). + Get("/rest/api/2/myself"). + Reply(http.StatusInternalServerError) + }, + data: fmt.Sprintf("jira token: %s", testToken), + verify: true, + wantResults: 1, + wantVerified: false, + wantVerificationErr: true, + }, + { + name: "found, verification error on timeout", + setup: func() { + gock.New(testEndpoint). + Get("/rest/api/2/myself"). + Reply(http.StatusOK). + Delay(2 * time.Second) + }, + data: fmt.Sprintf("jira token: %s", testToken), + verify: true, + wantResults: 1, + wantVerified: false, + wantVerificationErr: true, + }, + { + name: "found, no verify", + setup: func() {}, + data: fmt.Sprintf("jira token: %s", testToken), + verify: false, + wantResults: 1, + wantVerified: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gock.Flush() + tt.setup() + + ctx := context.Background() + if tt.wantVerificationErr { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, 100*time.Millisecond) + defer cancel() + } + + results, err := d.FromData(ctx, tt.verify, []byte(tt.data)) + require.NoError(t, err) + require.Len(t, results, tt.wantResults) + + for _, result := range results { + assert.Equal(t, detector_typepb.DetectorType_JiraDataCenterPAT, result.DetectorType) + assert.NotEmpty(t, result.Raw) + assert.Equal(t, tt.wantVerified, result.Verified) + assert.Equal(t, tt.wantVerificationErr, result.VerificationError() != nil) + if tt.wantExtraData != nil { + assert.Equal(t, tt.wantExtraData, result.ExtraData) + } + } + }) + } +} diff --git a/pkg/engine/defaults/defaults.go b/pkg/engine/defaults/defaults.go index 7d39fbdd931d..f94f3eec177a 100644 --- a/pkg/engine/defaults/defaults.go +++ b/pkg/engine/defaults/defaults.go @@ -397,6 +397,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipquality" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipstack" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jdbc" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiradatacenterpat" jiratokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1" jiratokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v2" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jotform" @@ -1275,6 +1276,7 @@ func buildDetectorList() []detectors.Detector { &ipquality.Scanner{}, &ipstack.Scanner{}, &jdbc.Scanner{}, + &jiradatacenterpat.Scanner{}, &jiratokenv1.Scanner{}, &jiratokenv2.Scanner{}, &jotform.Scanner{}, diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index b3aacd6cfcef..fa36ab44f692 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1378,6 +1378,7 @@ func TestEngineInitializesCloudProviderDetectors(t *testing.T) { detector_typepb.DetectorType_ArtifactoryAccessToken: {}, detector_typepb.DetectorType_ArtifactoryReferenceToken: {}, detector_typepb.DetectorType_TableauPersonalAccessToken: {}, + detector_typepb.DetectorType_JiraDataCenterPAT: {}, // these do not have any cloud endpoint } diff --git a/pkg/pb/detector_typepb/detector_type.pb.go b/pkg/pb/detector_typepb/detector_type.pb.go index c96f140fe86c..2d5a654aac29 100644 --- a/pkg/pb/detector_typepb/detector_type.pb.go +++ b/pkg/pb/detector_typepb/detector_type.pb.go @@ -1097,6 +1097,7 @@ const ( DetectorType_ArtifactoryReferenceToken DetectorType = 1042 DetectorType_DatadogApikey DetectorType = 1043 DetectorType_ShopifyOAuth DetectorType = 1044 + DetectorType_JiraDataCenterPAT DetectorType = 1045 ) // Enum value maps for DetectorType. @@ -2143,6 +2144,7 @@ var ( 1042: "ArtifactoryReferenceToken", 1043: "DatadogApikey", 1044: "ShopifyOAuth", + 1045: "JiraDataCenterPAT", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -3186,6 +3188,7 @@ var ( "ArtifactoryReferenceToken": 1042, "DatadogApikey": 1043, "ShopifyOAuth": 1044, + "JiraDataCenterPAT": 1045, } ) @@ -3221,7 +3224,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, 0xbb, 0x87, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, + 0x74, 0x79, 0x70, 0x65, 0x2a, 0xd3, 0x87, 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, @@ -4305,11 +4308,13 @@ var file_detector_type_proto_rawDesc = []byte{ 0x65, 0x6e, 0x63, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x92, 0x08, 0x12, 0x12, 0x0a, 0x0d, 0x44, 0x61, 0x74, 0x61, 0x64, 0x6f, 0x67, 0x41, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x10, 0x93, 0x08, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x68, 0x6f, 0x70, 0x69, 0x66, 0x79, 0x4f, 0x41, 0x75, 0x74, 0x68, - 0x10, 0x94, 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, + 0x10, 0x94, 0x08, 0x12, 0x16, 0x0a, 0x11, 0x4a, 0x69, 0x72, 0x61, 0x44, 0x61, 0x74, 0x61, 0x43, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x50, 0x41, 0x54, 0x10, 0x95, 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 ( diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index bc598c1860d7..6edb47eb62dd 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -496,16 +496,17 @@ var file_detectors_proto_rawDesc = []byte{ 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2a, - 0x51, 0x0a, 0x0b, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, + 0x5b, 0x0a, 0x0b, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 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, 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, + 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x4d, 0x4c, 0x10, 0x05, 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 ( diff --git a/proto/detector_type.proto b/proto/detector_type.proto index 0b566579530e..063bd1894616 100644 --- a/proto/detector_type.proto +++ b/proto/detector_type.proto @@ -1046,4 +1046,5 @@ enum DetectorType { ArtifactoryReferenceToken = 1042; DatadogApikey = 1043; ShopifyOAuth = 1044; + JiraDataCenterPAT = 1045; }