[INS-455] Unify common logic in Atlassian Data Center detectors (#4907)
* unify common logic in atlassian data center detectors * initialize url pat once * engine_test fix * remove bitbucketdatacenter from defaults test exclude list
This commit is contained in:
+6
-42
@@ -3,13 +3,13 @@ package bitbucketdatacenter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
regexp "github.com/wasilibs/go-re2"
|
regexp "github.com/wasilibs/go-re2"
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,8 +30,7 @@ var (
|
|||||||
// and are usually between the length of 40-50 character
|
// and are usually between the length of 40-50 character
|
||||||
// consisting of both alphanumeric and some special character like +, _, @ and etc
|
// consisting of both alphanumeric and some special character like +, _, @ and etc
|
||||||
userPat = regexp.MustCompile(`\b(BBDC-[A-Za-z0-9+/@_-]{40,50})(?:[^A-Za-z0-9+/@_-]|$)`)
|
userPat = regexp.MustCompile(`\b(BBDC-[A-Za-z0-9+/@_-]{40,50})(?:[^A-Za-z0-9+/@_-]|$)`)
|
||||||
|
urlPat = atlassiandatacenter.GetURLPat([]string{"atlassian", "bitbucket"})
|
||||||
urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"atlassian", "bitbucket"}) + `(https?://[a-zA-Z0-9.-]+(?::\d+)?)`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Scanner) Keywords() []string {
|
func (s Scanner) Keywords() []string {
|
||||||
@@ -52,24 +51,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
foundURLs := make(map[string]struct{})
|
endpoints := atlassiandatacenter.FindEndpoints(dataStr, urlPat, s.Endpoints)
|
||||||
for _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {
|
|
||||||
foundURLs[match[1]] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints := make([]string, 0, len(foundURLs))
|
|
||||||
for endpoint := range foundURLs {
|
|
||||||
endpoints = append(endpoints, endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uniqueUrls = make(map[string]struct{})
|
|
||||||
for _, endpoint := range s.Endpoints(endpoints...) {
|
|
||||||
uniqueUrls[endpoint] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create combination results that can be verified
|
// create combination results that can be verified
|
||||||
for secret := range uniqueSecretPat {
|
for secret := range uniqueSecretPat {
|
||||||
for bitBucketURL := range uniqueUrls {
|
for _, bitBucketURL := range endpoints {
|
||||||
s1 := detectors.Result{
|
s1 := detectors.Result{
|
||||||
DetectorType: detector_typepb.DetectorType_BitbucketDataCenter,
|
DetectorType: detector_typepb.DetectorType_BitbucketDataCenter,
|
||||||
Raw: []byte(secret),
|
Raw: []byte(secret),
|
||||||
@@ -109,30 +95,8 @@ func verifyMatch(ctx context.Context, client *http.Client, secretPat, baseURL st
|
|||||||
q.Set("limit", "1")
|
q.Set("limit", "1")
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
|
isVerified, _, err := atlassiandatacenter.MakeVerifyRequest(ctx, client, u.String(), secretPat)
|
||||||
if err != nil {
|
return isVerified, err
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", secretPat))
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK:
|
|
||||||
return true, nil
|
|
||||||
case http.StatusUnauthorized:
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Scanner) Type() detector_typepb.DetectorType {
|
func (s Scanner) Type() detector_typepb.DetectorType {
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package atlassiandatacenter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
regexp "github.com/wasilibs/go-re2"
|
||||||
|
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDCTokenPat returns a compiled regex that matches Atlassian Data Center PATs
|
||||||
|
// (Jira DC and Confluence DC style) scoped to the given keyword prefixes.
|
||||||
|
//
|
||||||
|
// PATs are 44-char base64 strings decoding to "<numeric-id>:<random-bytes>".
|
||||||
|
// The first character is always M, N, or O because the numeric ID begins with
|
||||||
|
// an ASCII digit (0x30–0x39). The trailing boundary prevents matching substrings
|
||||||
|
// of longer base64 strings or base64-padded tokens.
|
||||||
|
//
|
||||||
|
// This does not apply to Bitbucket DC tokens, which use a BBDC- prefix format.
|
||||||
|
func GetDCTokenPat(prefixes []string) *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(
|
||||||
|
detectors.PrefixRegex(prefixes) + `\b([MNO][A-Za-z0-9+/]{43})(?:[^A-Za-z0-9+/=]|\z)`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURLPat returns a compiled regex that matches self-hosted Atlassian instance
|
||||||
|
// URLs (scheme + alphanumeric-starting host + optional port up to 5 digits),
|
||||||
|
// scoped to the given keyword prefixes. Callers should store the result in a
|
||||||
|
// package-level var so the regex is compiled once at init time rather than per chunk.
|
||||||
|
func GetURLPat(prefixes []string) *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(detectors.PrefixRegex(prefixes) + `(https?://[a-zA-Z0-9][a-zA-Z0-9.\-]*(?::\d{1,5})?)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEndpoints extracts all URLs matching urlPat from data, passes them through
|
||||||
|
// the resolve function (typically s.Endpoints), deduplicates the results, and
|
||||||
|
// returns them as a slice with trailing slashes stripped.
|
||||||
|
func FindEndpoints(data string, urlPat *regexp.Regexp, resolve func(...string) []string) []string {
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, m := range urlPat.FindAllStringSubmatch(data, -1) {
|
||||||
|
seen[m[1]] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make([]string, 0, len(seen))
|
||||||
|
for u := range seen {
|
||||||
|
raw = append(raw, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved := make(map[string]struct{})
|
||||||
|
for _, u := range resolve(raw...) {
|
||||||
|
resolved[strings.TrimRight(u, "/")] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0, len(resolved))
|
||||||
|
for u := range resolved {
|
||||||
|
result = append(result, u)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStructuralPAT decodes a candidate base64 string and checks that it matches
|
||||||
|
// the "<numeric id>:<random bytes>" structure used by Jira and Confluence DC PATs:
|
||||||
|
// one or more ASCII digits, a colon, then at least one more byte.
|
||||||
|
func IsStructuralPAT(candidate string) bool {
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(candidate)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
colon := bytes.IndexByte(raw, ':')
|
||||||
|
if colon <= 0 || colon == len(raw)-1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, b := range raw[:colon] {
|
||||||
|
if b < '0' || b > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeVerifyRequest sends a Bearer-authenticated GET request to fullURL and
|
||||||
|
// interprets the response:
|
||||||
|
// - 200: returns (true, decoded JSON body as map or nil if unparseable, nil)
|
||||||
|
// - 401: returns (false, nil, nil)
|
||||||
|
// - other: returns (false, nil, error describing the unexpected status)
|
||||||
|
//
|
||||||
|
// A non-nil error is also returned for network failures.
|
||||||
|
// Callers that need fields from the response body (e.g. display name, email)
|
||||||
|
// can read them from the returned map; callers that don't need the body can
|
||||||
|
// ignore it.
|
||||||
|
func MakeVerifyRequest(ctx context.Context, client *http.Client, fullURL, token string) (bool, map[string]any, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+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 body map[string]any
|
||||||
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
||||||
|
return true, body, nil
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return false, nil, nil
|
||||||
|
default:
|
||||||
|
return false, nil, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
package atlassiandatacenter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/h2non/gock.v1"
|
||||||
|
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Real-format sample PATs that decode to "<numeric id>:<random bytes>".
|
||||||
|
const (
|
||||||
|
// Jira DC sample.
|
||||||
|
jiraToken = "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M"
|
||||||
|
|
||||||
|
// Confluence DC samples.
|
||||||
|
confluenceToken1 = "NTk3MjQzOTIyNTAwOtFOuTsHRIp1E81GApKpC2xpEzfz"
|
||||||
|
confluenceToken2 = "NDc4MjM3OTUxMzk2OopoSkTDTnBcWIw0Wa4bico9zOLK"
|
||||||
|
|
||||||
|
// 44-char base64 that starts with [MNO] (passes the regex) but decodes
|
||||||
|
// to bytes with no colon — must be rejected by IsStructuralPAT.
|
||||||
|
nonStructural = "MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encode(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
|
||||||
|
|
||||||
|
func TestIsStructuralPAT(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
candidate string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid real Jira token",
|
||||||
|
candidate: jiraToken,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid real Confluence token 1",
|
||||||
|
candidate: confluenceToken1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid real Confluence token 2",
|
||||||
|
candidate: confluenceToken2,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid - digits before colon",
|
||||||
|
candidate: encode([]byte("123456789012:01234567890123456789")),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid base64",
|
||||||
|
candidate: "!!!not-base64!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no colon in decoded bytes",
|
||||||
|
candidate: encode([]byte("588925395905012345678901234567890")),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colon at position 0",
|
||||||
|
candidate: encode([]byte(":01234567890123456789012345678901")),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colon at last position",
|
||||||
|
candidate: encode([]byte("58892539590501234567890123456789:")),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// decodes to "0a:xxx..." — 'a' is not a digit
|
||||||
|
name: "non-digit before colon",
|
||||||
|
candidate: "MGE6eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-structural: passes regex but no colon in decoded bytes",
|
||||||
|
candidate: nonStructural,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, IsStructuralPAT(tt.candidate))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDCTokenPat(t *testing.T) {
|
||||||
|
pat := GetDCTokenPat([]string{"jira", "atlassian"})
|
||||||
|
|
||||||
|
// Known-valid token — must match and capture exactly the token (no trailing char).
|
||||||
|
m := pat.FindStringSubmatch("jira token: " + jiraToken)
|
||||||
|
require.NotNil(t, m, "expected a match")
|
||||||
|
assert.Equal(t, jiraToken, m[1], "captured group should be exactly the token")
|
||||||
|
|
||||||
|
// Token followed by newline — must match and NOT capture the newline.
|
||||||
|
m2 := pat.FindStringSubmatch("jira token: " + jiraToken + "\n")
|
||||||
|
require.NotNil(t, m2)
|
||||||
|
assert.Equal(t, jiraToken, m2[1], "captured group must not include the trailing newline")
|
||||||
|
|
||||||
|
// Token starts with 'A' — not M/N/O — must not match.
|
||||||
|
assert.Nil(t, pat.FindStringSubmatch("jira token: ATg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M"))
|
||||||
|
|
||||||
|
// Token followed by base64 padding — trailing boundary must reject it.
|
||||||
|
assert.Nil(t, pat.FindStringSubmatch("jira token: "+jiraToken+"="))
|
||||||
|
|
||||||
|
// Token followed by more base64 chars — must not match (longer string).
|
||||||
|
assert.Nil(t, pat.FindStringSubmatch("jira token: "+jiraToken+"AAAA"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindEndpoints(t *testing.T) {
|
||||||
|
urlPat := GetURLPat([]string{"jira", "atlassian"})
|
||||||
|
|
||||||
|
// identity resolver: returns exactly what it receives (simulates UseFoundEndpoints only)
|
||||||
|
identity := func(urls ...string) []string { return urls }
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
resolve func(...string) []string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "URL near keyword is returned",
|
||||||
|
data: "jira url: https://jira.corp.com",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{"https://jira.corp.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL not near any keyword is ignored",
|
||||||
|
data: "unrelated url: https://example.com",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate URLs in data are deduplicated",
|
||||||
|
data: "jira: https://jira.corp.com\natlassian: https://jira.corp.com",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{"https://jira.corp.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing slash is stripped",
|
||||||
|
data: "jira url: https://jira.corp.com/",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{"https://jira.corp.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with port is accepted",
|
||||||
|
data: "jira url: https://jira.corp.com:8443",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{"https://jira.corp.com:8443"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple distinct URLs are all returned",
|
||||||
|
data: "jira prod: https://jira.prod.com\natlassian staging: https://jira.staging.com",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{"https://jira.prod.com", "https://jira.staging.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resolve can inject configured endpoints not in data",
|
||||||
|
data: "no urls here but jira keyword present",
|
||||||
|
resolve: func(urls ...string) []string {
|
||||||
|
return append(urls, "https://configured.jira.com")
|
||||||
|
},
|
||||||
|
want: []string{"https://configured.jira.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resolve can filter out URLs",
|
||||||
|
data: "jira url: https://jira.corp.com",
|
||||||
|
resolve: func(urls ...string) []string {
|
||||||
|
return []string{} // simulate UseFoundEndpoints(false) with no configured endpoint
|
||||||
|
},
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no data returns empty slice",
|
||||||
|
data: "",
|
||||||
|
resolve: identity,
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := FindEndpoints(tt.data, urlPat, tt.resolve)
|
||||||
|
assert.ElementsMatch(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeVerifyRequest(t *testing.T) {
|
||||||
|
const testURL = "http://dc.example.com/rest/api/test"
|
||||||
|
const testToken = "NTg4OTI1Mzk1OTA1OiBb9S4WPEoK6cmOe6pq6VO0lt6M"
|
||||||
|
|
||||||
|
t.Run("200: verified=true, body decoded", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
gock.New("http://dc.example.com").
|
||||||
|
Get("/rest/api/test").
|
||||||
|
MatchHeader("Authorization", "Bearer "+testToken).
|
||||||
|
MatchHeader("Accept", "application/json").
|
||||||
|
Reply(http.StatusOK).
|
||||||
|
JSON(map[string]any{"displayName": "Alice", "emailAddress": "[email protected]"})
|
||||||
|
|
||||||
|
verified, body, err := MakeVerifyRequest(context.Background(), client, testURL, testToken)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, verified)
|
||||||
|
require.NotNil(t, body)
|
||||||
|
assert.Equal(t, "Alice", body["displayName"])
|
||||||
|
assert.Equal(t, "[email protected]", body["emailAddress"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("200: verified=true, body nil when response is not JSON", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
gock.New("http://dc.example.com").
|
||||||
|
Get("/rest/api/test").
|
||||||
|
Reply(http.StatusOK).
|
||||||
|
BodyString("not json")
|
||||||
|
|
||||||
|
verified, body, err := MakeVerifyRequest(context.Background(), client, testURL, testToken)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, verified)
|
||||||
|
assert.Nil(t, body)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("401: verified=false, no error", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
gock.New("http://dc.example.com").
|
||||||
|
Get("/rest/api/test").
|
||||||
|
Reply(http.StatusUnauthorized)
|
||||||
|
|
||||||
|
verified, body, err := MakeVerifyRequest(context.Background(), client, testURL, testToken)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, verified)
|
||||||
|
assert.Nil(t, body)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unexpected status: verified=false, error returned", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
gock.New("http://dc.example.com").
|
||||||
|
Get("/rest/api/test").
|
||||||
|
Reply(http.StatusInternalServerError)
|
||||||
|
|
||||||
|
verified, body, err := MakeVerifyRequest(context.Background(), client, testURL, testToken)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.False(t, verified)
|
||||||
|
assert.Nil(t, body)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uses GET method", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
// gock.Get only matches GET; a POST would not match and would error.
|
||||||
|
gock.New("http://dc.example.com").
|
||||||
|
Get("/rest/api/test").
|
||||||
|
Reply(http.StatusOK)
|
||||||
|
|
||||||
|
_, _, err := MakeVerifyRequest(context.Background(), client, testURL, testToken)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, gock.IsDone(), "gock interceptor was not matched — request may not have been GET")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("propagates network error", func(t *testing.T) {
|
||||||
|
client := common.SaneHttpClient()
|
||||||
|
defer gock.Off()
|
||||||
|
defer gock.RestoreClient(client)
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel() // cancel immediately to force a network error
|
||||||
|
|
||||||
|
verified, body, err := MakeVerifyRequest(ctx, client, testURL, testToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, verified)
|
||||||
|
assert.Nil(t, body)
|
||||||
|
})
|
||||||
|
}
|
||||||
+10
-66
@@ -1,19 +1,15 @@
|
|||||||
package confluencedatacenter
|
package confluencedatacenter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
regexp "github.com/wasilibs/go-re2"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,12 +31,9 @@ var (
|
|||||||
|
|
||||||
keywords = []string{"confluence", "atlassian", "wiki"}
|
keywords = []string{"confluence", "atlassian", "wiki"}
|
||||||
|
|
||||||
// 44-char base64 PAT; decoded form must match the structural check below.
|
// 44-char base64 PAT; decoded form must match the structural check in atlassiandatacenter.IsStructuralPAT.
|
||||||
tokenPat = regexp.MustCompile(detectors.PrefixRegex(keywords) + `\b([MNO][A-Za-z0-9+/]{43})(?:[^A-Za-z0-9+/=]|\z)`)
|
tokenPat = atlassiandatacenter.GetDCTokenPat(keywords)
|
||||||
|
urlPat = atlassiandatacenter.GetURLPat(keywords)
|
||||||
// Self-hosted instance URL: scheme + host + optional port. Keyword-scoped
|
|
||||||
// so unrelated URLs in the same chunk don't get paired with tokens.
|
|
||||||
urlPat = regexp.MustCompile(detectors.PrefixRegex(keywords) + `\b(https?://[a-zA-Z0-9.\-]+(?::\d+)?)\b`)
|
|
||||||
|
|
||||||
invalidHosts = simple.NewCache[struct{}]()
|
invalidHosts = simple.NewCache[struct{}]()
|
||||||
errNoHost = errors.New("no such host")
|
errNoHost = errors.New("no such host")
|
||||||
@@ -65,26 +58,6 @@ func (s Scanner) getClient() *http.Client {
|
|||||||
return defaultClient
|
return defaultClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// isStructuralPAT decodes a candidate base64 string and checks that it matches
|
|
||||||
// the "<numeric id>:<random bytes>" structure used by Confluence DC PATs:
|
|
||||||
// one or more ASCII digits, a colon, then at least one more byte.
|
|
||||||
func isStructuralPAT(candidate string) bool {
|
|
||||||
raw, err := base64.StdEncoding.DecodeString(candidate)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
colon := bytes.IndexByte(raw, ':')
|
|
||||||
if colon <= 0 || colon == len(raw)-1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, b := range raw[:colon] {
|
|
||||||
if b < '0' || b > '9' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||||
dataStr := string(data)
|
dataStr := string(data)
|
||||||
|
|
||||||
@@ -93,7 +66,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
if _, seen := uniqueTokens[m[1]]; seen {
|
if _, seen := uniqueTokens[m[1]]; seen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isStructuralPAT(m[1]) {
|
if atlassiandatacenter.IsStructuralPAT(m[1]) {
|
||||||
uniqueTokens[m[1]] = struct{}{}
|
uniqueTokens[m[1]] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,20 +74,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
foundURLs := make([]string, 0)
|
allURLs := atlassiandatacenter.FindEndpoints(dataStr, urlPat, s.Endpoints)
|
||||||
for _, m := range urlPat.FindAllStringSubmatch(dataStr, -1) {
|
|
||||||
foundURLs = append(foundURLs, m[1])
|
|
||||||
}
|
|
||||||
uniqueURLs := make(map[string]struct{})
|
|
||||||
for _, endpoint := range s.Endpoints(foundURLs...) {
|
|
||||||
uniqueURLs[strings.TrimRight(endpoint, "/")] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter hosts cached as unreachable from prior calls once up front.
|
// Filter hosts cached as unreachable from prior calls once up front.
|
||||||
// invalidHosts may also grow during this call (see the verify branch
|
// invalidHosts may also grow during this call (see the verify branch
|
||||||
// below); those are skipped lazily inside the inner loop.
|
// below); those are skipped lazily inside the inner loop.
|
||||||
liveURLs := make([]string, 0, len(uniqueURLs))
|
liveURLs := make([]string, 0, len(allURLs))
|
||||||
for u := range uniqueURLs {
|
for _, u := range allURLs {
|
||||||
if !invalidHosts.Exists(u) {
|
if !invalidHosts.Exists(u) {
|
||||||
liveURLs = append(liveURLs, u)
|
liveURLs = append(liveURLs, u)
|
||||||
}
|
}
|
||||||
@@ -175,35 +141,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
|
|
||||||
func verifyPAT(ctx context.Context, client *http.Client, baseURL, token string) (bool, error) {
|
func verifyPAT(ctx context.Context, client *http.Client, baseURL, token string) (bool, error) {
|
||||||
endpoint := strings.TrimRight(baseURL, "/") + "/rest/api/user/current"
|
endpoint := strings.TrimRight(baseURL, "/") + "/rest/api/user/current"
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
isVerified, _, err := atlassiandatacenter.MakeVerifyRequest(ctx, client, endpoint, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no such host") {
|
if strings.Contains(err.Error(), "no such host") {
|
||||||
return false, errNoHost
|
return false, errNoHost
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer func() {
|
return isVerified, nil
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK:
|
|
||||||
return true, nil
|
|
||||||
case http.StatusUnauthorized:
|
|
||||||
// Auth header outright rejected — unambiguously an invalid credential.
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
// 403 included: /rest/api/user/current should always be readable by a
|
|
||||||
// valid PAT, so a Forbidden here signals something unexpected rather
|
|
||||||
// than a definitively invalid token.
|
|
||||||
return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+18
-79
@@ -1,17 +1,11 @@
|
|||||||
package jiradatacenterpat
|
package jiradatacenterpat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
regexp "github.com/wasilibs/go-re2"
|
|
||||||
|
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,12 +25,14 @@ var (
|
|||||||
var (
|
var (
|
||||||
defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
|
defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
|
||||||
|
|
||||||
|
keywords = []string{"jira", "atlassian"}
|
||||||
|
|
||||||
// PATs are base64-encoded strings of the form <12-digit-id>:<20-random-bytes> (33 bytes, 44 chars, no padding).
|
// 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.
|
// 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.
|
// 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 /.
|
// 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)`)
|
patPat = atlassiandatacenter.GetDCTokenPat(keywords)
|
||||||
urlPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira", "atlassian"}) + `(https?://[A-Za-z0-9][A-Za-z0-9.\-]*(?::\d{1,5})?)`)
|
urlPat = atlassiandatacenter.GetURLPat(keywords)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Scanner) getClient() *http.Client {
|
func (s Scanner) getClient() *http.Client {
|
||||||
@@ -48,7 +44,7 @@ func (s Scanner) getClient() *http.Client {
|
|||||||
|
|
||||||
// Keywords are used for efficiently pre-filtering chunks.
|
// Keywords are used for efficiently pre-filtering chunks.
|
||||||
func (s Scanner) Keywords() []string {
|
func (s Scanner) Keywords() []string {
|
||||||
return []string{"jira", "atlassian"}
|
return keywords
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromData will find and optionally verify Jira Data Center PAT secrets in a given set of bytes.
|
// FromData will find and optionally verify Jira Data Center PAT secrets in a given set of bytes.
|
||||||
@@ -57,23 +53,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
|
|
||||||
tokens := make(map[string]struct{})
|
tokens := make(map[string]struct{})
|
||||||
for _, match := range patPat.FindAllStringSubmatch(dataStr, -1) {
|
for _, match := range patPat.FindAllStringSubmatch(dataStr, -1) {
|
||||||
if isStructuralPAT(match[1]) {
|
if atlassiandatacenter.IsStructuralPAT(match[1]) {
|
||||||
tokens[match[1]] = struct{}{}
|
tokens[match[1]] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueURLs := make(map[string]struct{})
|
endpoints := atlassiandatacenter.FindEndpoints(dataStr, urlPat, s.Endpoints)
|
||||||
for _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {
|
|
||||||
uniqueURLs[match[1]] = struct{}{}
|
|
||||||
}
|
|
||||||
foundURLs := make([]string, 0, len(uniqueURLs))
|
|
||||||
for url := range uniqueURLs {
|
|
||||||
foundURLs = append(foundURLs, url)
|
|
||||||
}
|
|
||||||
endpoints := make(map[string]struct{})
|
|
||||||
for _, endpoint := range s.Endpoints(foundURLs...) {
|
|
||||||
endpoints[endpoint] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for token := range tokens {
|
for token := range tokens {
|
||||||
if len(endpoints) == 0 {
|
if len(endpoints) == 0 {
|
||||||
@@ -87,7 +72,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
s1 := detectors.Result{
|
s1 := detectors.Result{
|
||||||
DetectorType: detector_typepb.DetectorType_JiraDataCenterPAT,
|
DetectorType: detector_typepb.DetectorType_JiraDataCenterPAT,
|
||||||
Raw: []byte(token),
|
Raw: []byte(token),
|
||||||
@@ -127,65 +112,19 @@ func verifyPAT(ctx context.Context, client *http.Client, baseURL, token string)
|
|||||||
}
|
}
|
||||||
u.Path = "/rest/api/2/myself"
|
u.Path = "/rest/api/2/myself"
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
|
isVerified, body, err := atlassiandatacenter.MakeVerifyRequest(ctx, client, u.String(), token)
|
||||||
if err != nil {
|
if err != nil || !isVerified {
|
||||||
return false, nil, err
|
return isVerified, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Accept", "application/json")
|
extraData := map[string]string{"endpoint": baseURL}
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
if name, ok := body["displayName"].(string); ok {
|
||||||
|
extraData["display_name"] = name
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
}
|
||||||
defer func() {
|
if email, ok := body["emailAddress"].(string); ok {
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
extraData["email_address"] = email
|
||||||
_ = resp.Body.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK:
|
|
||||||
var result map[string]any
|
|
||||||
extraData := map[string]string{
|
|
||||||
"endpoint": baseURL,
|
|
||||||
}
|
|
||||||
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, extraData, nil
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
return true, extraData, nil
|
||||||
|
|
||||||
// isStructuralPAT decodes a candidate base64 string and checks that it matches
|
|
||||||
// the "<numeric id>:<random bytes>" structure used by Jira DC PATs:
|
|
||||||
// one or more ASCII digits, a colon, then at least one more byte.
|
|
||||||
func isStructuralPAT(candidate string) bool {
|
|
||||||
raw, err := base64.StdEncoding.DecodeString(candidate)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
colon := bytes.IndexByte(raw, ':')
|
|
||||||
if colon <= 0 || colon == len(raw)-1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, b := range raw[:colon] {
|
|
||||||
if b < '0' || b > '9' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Scanner) Type() detector_typepb.DetectorType {
|
func (s Scanner) Type() detector_typepb.DetectorType {
|
||||||
-59
@@ -2,7 +2,6 @@ package jiradatacenterpat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -275,64 +274,6 @@ func TestJiraDataCenterPAT_FromData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsStructuralPAT(t *testing.T) {
|
|
||||||
encode := func(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
|
|
||||||
|
|
||||||
// helper to build a 33-byte payload with a numeric id and random suffix
|
|
||||||
numericIDPayload := func(id, suffix string) []byte {
|
|
||||||
return []byte(id + ":" + suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
candidate string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid real token",
|
|
||||||
candidate: testToken,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid - digits before colon",
|
|
||||||
candidate: encode(numericIDPayload("123456789012", "01234567890123456789")),
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid base64",
|
|
||||||
candidate: "!!!not-base64!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no colon",
|
|
||||||
candidate: encode([]byte("588925395905012345678901234567890")),
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "colon at position 0",
|
|
||||||
candidate: encode([]byte(":01234567890123456789012345678901")),
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "colon at last position",
|
|
||||||
candidate: encode([]byte("58892539590501234567890123456789:")),
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-digit before colon",
|
|
||||||
// decodes to "0a:xxx..." — 'a' is not a digit
|
|
||||||
candidate: "MGE6eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.want, isStructuralPAT(tt.candidate))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJiraDataCenterPAT_NoURL(t *testing.T) {
|
func TestJiraDataCenterPAT_NoURL(t *testing.T) {
|
||||||
d := Scanner{client: common.SaneHttpClient()}
|
d := Scanner{client: common.SaneHttpClient()}
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ import (
|
|||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/billomat"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/billomat"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bingsubscriptionkey"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bingsubscriptionkey"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbar"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbar"
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter/bitbucketdatacenter"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbucketapppassword"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbucketapppassword"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitcoinaverage"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitcoinaverage"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitfinex"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitfinex"
|
||||||
@@ -186,7 +187,7 @@ import (
|
|||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commercejs"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commercejs"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commodities"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commodities"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/companyhub"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/companyhub"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/confluencedatacenter"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter/confluencedatacenter"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/confluent"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/confluent"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/contentfulpersonalaccesstoken"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/contentfulpersonalaccesstoken"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/conversiontools"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/conversiontools"
|
||||||
@@ -399,7 +400,7 @@ import (
|
|||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipquality"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipquality"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipstack"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipstack"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jdbc"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jdbc"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiradatacenterpat"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassiandatacenter/jiradatacenterpat"
|
||||||
jiratokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1"
|
jiratokenv1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1"
|
||||||
jiratokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v2"
|
jiratokenv2 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v2"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jotform"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jotform"
|
||||||
@@ -968,6 +969,7 @@ func buildDetectorList() []detectors.Detector {
|
|||||||
&billomat.Scanner{},
|
&billomat.Scanner{},
|
||||||
&bingsubscriptionkey.Scanner{},
|
&bingsubscriptionkey.Scanner{},
|
||||||
&bitbar.Scanner{},
|
&bitbar.Scanner{},
|
||||||
|
&bitbucketdatacenter.Scanner{},
|
||||||
&bitbucketapppassword.Scanner{},
|
&bitbucketapppassword.Scanner{},
|
||||||
&bitcoinaverage.Scanner{},
|
&bitcoinaverage.Scanner{},
|
||||||
&bitfinex.Scanner{},
|
&bitfinex.Scanner{},
|
||||||
|
|||||||
@@ -116,16 +116,15 @@ var excludedFromDefaultList = map[detector_typepb.DetectorType]struct{}{
|
|||||||
// to buildDetectorList() — discovered by TestAllDetectorTypesAreInDefaultList.
|
// to buildDetectorList() — discovered by TestAllDetectorTypesAreInDefaultList.
|
||||||
// They are not added immediately out of caution for the impact on customers/users.
|
// They are not added immediately out of caution for the impact on customers/users.
|
||||||
// Remove each entry once its detector has been carefully added.
|
// Remove each entry once its detector has been carefully added.
|
||||||
detector_typepb.DetectorType_BitbucketDataCenter: {},
|
detector_typepb.DetectorType_DatadogApikey: {},
|
||||||
detector_typepb.DetectorType_DatadogApikey: {},
|
detector_typepb.DetectorType_Guru: {},
|
||||||
detector_typepb.DetectorType_Guru: {},
|
detector_typepb.DetectorType_IPInfo: {},
|
||||||
detector_typepb.DetectorType_IPInfo: {},
|
detector_typepb.DetectorType_Lob: {},
|
||||||
detector_typepb.DetectorType_Lob: {},
|
detector_typepb.DetectorType_Rev: {},
|
||||||
detector_typepb.DetectorType_Rev: {},
|
detector_typepb.DetectorType_TLy: {},
|
||||||
detector_typepb.DetectorType_TLy: {},
|
detector_typepb.DetectorType_Tru: {},
|
||||||
detector_typepb.DetectorType_Tru: {},
|
detector_typepb.DetectorType_User: {},
|
||||||
detector_typepb.DetectorType_User: {},
|
detector_typepb.DetectorType_Wit: {},
|
||||||
detector_typepb.DetectorType_Wit: {},
|
|
||||||
|
|
||||||
// Reserved / special types.
|
// Reserved / special types.
|
||||||
detector_typepb.DetectorType_CustomRegex: {}, // added dynamically via engine config, not via buildDetectorList()
|
detector_typepb.DetectorType_CustomRegex: {}, // added dynamically via engine config, not via buildDetectorList()
|
||||||
|
|||||||
@@ -1381,6 +1381,7 @@ func TestEngineInitializesCloudProviderDetectors(t *testing.T) {
|
|||||||
detector_typepb.DetectorType_HashiCorpVaultAuth: {},
|
detector_typepb.DetectorType_HashiCorpVaultAuth: {},
|
||||||
detector_typepb.DetectorType_JiraDataCenterPAT: {},
|
detector_typepb.DetectorType_JiraDataCenterPAT: {},
|
||||||
detector_typepb.DetectorType_ConfluenceDataCenter: {},
|
detector_typepb.DetectorType_ConfluenceDataCenter: {},
|
||||||
|
detector_typepb.DetectorType_BitbucketDataCenter: {},
|
||||||
// these do not have any cloud endpoint
|
// these do not have any cloud endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user