Skip to content

Commit 39d2453

Browse files
kashifkhan0771rgmz
andauthored
fixed sentry auth token detector (#3827)
* fixed sentry auth token detector * not sure why this is failing * Updated http client logic Co-authored-by: Richard Gomez <[email protected]> * resolved comments * improved decoding logic * removed response type * splitted to two versions * splitted to two versions * this is confusing error --------- Co-authored-by: Richard Gomez <[email protected]>
1 parent 1fc8961 commit 39d2453

9 files changed

+500
-196
lines changed

pkg/detectors/sentrytoken/sentrytoken.go

-132
This file was deleted.
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package sentrytoken
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"strings"
10+
11+
regexp "github.com/wasilibs/go-re2"
12+
13+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
14+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
15+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
16+
)
17+
18+
type Scanner struct {
19+
client *http.Client
20+
}
21+
22+
type Organization struct {
23+
ID string `json:"id"`
24+
Name string `json:"name"`
25+
}
26+
27+
// Ensure the Scanner satisfies the interface at compile time.
28+
var _ detectors.Detector = (*Scanner)(nil)
29+
var _ detectors.Versioner = (*Scanner)(nil)
30+
31+
var (
32+
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
33+
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sentry"}) + `\b([a-f0-9]{64})\b`)
34+
35+
forbiddenError = "You do not have permission to perform this action."
36+
)
37+
38+
func (s Scanner) Version() int {
39+
return 1
40+
}
41+
42+
// Keywords are used for efficiently pre-filtering chunks.
43+
// Use identifiers in the secret preferably, or the provider name.
44+
func (s Scanner) Keywords() []string {
45+
return []string{"sentry"}
46+
}
47+
48+
// FromData will find and optionally verify SentryToken secrets in a given set of bytes.
49+
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
50+
dataStr := string(data)
51+
52+
// find all unique auth tokens
53+
var uniqueAuthTokens = make(map[string]struct{})
54+
55+
for _, authToken := range keyPat.FindAllStringSubmatch(dataStr, -1) {
56+
uniqueAuthTokens[authToken[1]] = struct{}{}
57+
}
58+
59+
for authToken := range uniqueAuthTokens {
60+
s1 := detectors.Result{
61+
DetectorType: detectorspb.DetectorType_SentryToken,
62+
Raw: []byte(authToken),
63+
}
64+
65+
if verify {
66+
if s.client == nil {
67+
s.client = common.SaneHttpClient()
68+
}
69+
extraData, isVerified, verificationErr := VerifyToken(ctx, s.client, authToken)
70+
s1.Verified = isVerified
71+
s1.SetVerificationError(verificationErr, authToken)
72+
s1.ExtraData = extraData
73+
}
74+
75+
results = append(results, s1)
76+
}
77+
78+
return results, nil
79+
}
80+
81+
func VerifyToken(ctx context.Context, client *http.Client, token string) (map[string]string, bool, error) {
82+
// api docs: https://docs.sentry.io/api/organizations/
83+
// this api will return 200 for user auth tokens with scope of org:<>
84+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://sentry.io/api/0/organizations/", nil)
85+
if err != nil {
86+
return nil, false, err
87+
}
88+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
89+
90+
resp, err := client.Do(req)
91+
if err != nil {
92+
return nil, false, err
93+
}
94+
defer func() {
95+
_, _ = io.Copy(io.Discard, resp.Body)
96+
_ = resp.Body.Close()
97+
}()
98+
99+
switch resp.StatusCode {
100+
case http.StatusOK:
101+
var organizations []Organization
102+
if err = json.NewDecoder(resp.Body).Decode(&organizations); err != nil {
103+
return nil, false, err
104+
}
105+
106+
var extraData = make(map[string]string)
107+
for _, org := range organizations {
108+
extraData[fmt.Sprintf("orginzation_%s", org.ID)] = org.Name
109+
}
110+
111+
return extraData, true, nil
112+
case http.StatusForbidden:
113+
var APIResp interface{}
114+
if err = json.NewDecoder(resp.Body).Decode(&APIResp); err != nil {
115+
return nil, false, err
116+
}
117+
118+
// if response contain the forbiddenError message it means the token is active but does not have the right scope for this API call
119+
if strings.Contains(fmt.Sprintf("%v", APIResp), forbiddenError) {
120+
return nil, true, nil
121+
}
122+
123+
return nil, false, nil
124+
case http.StatusUnauthorized:
125+
return nil, false, nil
126+
default:
127+
return nil, false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
128+
}
129+
}
130+
131+
func (s Scanner) Type() detectorspb.DetectorType {
132+
return detectorspb.DetectorType_SentryToken
133+
}
134+
135+
func (s Scanner) Description() string {
136+
return "Sentry is an error tracking service that helps developers monitor and fix crashes in real time. Sentry tokens can be used to access and manage projects and organizations within Sentry."
137+
}

pkg/detectors/sentrytoken/sentrytoken_integration_test.go pkg/detectors/sentrytoken/v1/sentrytoken_integration_test.go

+1-50
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func TestSentryToken_FromChunk(t *testing.T) {
5252
{
5353
DetectorType: detectorspb.DetectorType_SentryToken,
5454
Verified: true,
55+
ExtraData: map[string]string{"orginzation_4508567357947904": "Truffle Security"},
5556
},
5657
},
5758
wantErr: false,
@@ -106,56 +107,6 @@ func TestSentryToken_FromChunk(t *testing.T) {
106107
wantErr: false,
107108
wantVerificationErr: true,
108109
},
109-
{
110-
name: "found, good key but wrong scope",
111-
s: Scanner{client: common.ConstantResponseHttpClient(403, responseBody403)},
112-
args: args{
113-
ctx: context.Background(),
114-
data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)),
115-
verify: true,
116-
},
117-
want: []detectors.Result{
118-
{
119-
DetectorType: detectorspb.DetectorType_SentryToken,
120-
Verified: true,
121-
},
122-
},
123-
wantErr: false,
124-
},
125-
{
126-
name: "found, account deactivated",
127-
s: Scanner{client: common.ConstantResponseHttpClient(200, responseAccountDeactivated)},
128-
args: args{
129-
ctx: context.Background(),
130-
data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)),
131-
verify: true,
132-
},
133-
want: []detectors.Result{
134-
{
135-
DetectorType: detectorspb.DetectorType_SentryToken,
136-
Verified: false,
137-
},
138-
},
139-
wantErr: false,
140-
wantVerificationErr: true,
141-
},
142-
{
143-
name: "found, account deactivated",
144-
s: Scanner{client: common.ConstantResponseHttpClient(200, responseEmpty)},
145-
args: args{
146-
ctx: context.Background(),
147-
data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)),
148-
verify: true,
149-
},
150-
want: []detectors.Result{
151-
{
152-
DetectorType: detectorspb.DetectorType_SentryToken,
153-
Verified: false,
154-
},
155-
},
156-
wantErr: false,
157-
wantVerificationErr: true,
158-
},
159110
{
160111
name: "not found",
161112
s: Scanner{},

pkg/detectors/sentrytoken/sentrytoken_test.go pkg/detectors/sentrytoken/v1/sentrytoken_test.go

+10-11
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ import (
1212
)
1313

1414
var (
15-
validPattern = "ad00eba0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20"
16-
invalidPattern = "ad00e?a0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20"
17-
keyword = "sentrytoken"
15+
validPattern = `
16+
sentry_token := ad00eba0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20
17+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sentry_token))
18+
`
19+
invalidPattern = "28ab769ecf2b465fake#0ea877d6494feffe5017a5824ec2920f24451423fake"
20+
keyword = "sentry"
21+
token = "ad00eba0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20"
1822
)
1923

2024
func TestSentryToken_Pattern(t *testing.T) {
@@ -27,18 +31,13 @@ func TestSentryToken_Pattern(t *testing.T) {
2731
}{
2832
{
2933
name: "valid pattern - with keyword sentrytoken",
30-
input: fmt.Sprintf("%s token = '%s'", keyword, validPattern),
31-
want: []string{validPattern},
34+
input: validPattern,
35+
want: []string{token},
3236
},
3337
{
3438
name: "valid pattern - ignore duplicate",
3539
input: fmt.Sprintf("%s token = '%s' | '%s'", keyword, validPattern, validPattern),
36-
want: []string{validPattern},
37-
},
38-
{
39-
name: "valid pattern - key out of prefix range",
40-
input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validPattern),
41-
want: []string{},
40+
want: []string{token},
4241
},
4342
{
4443
name: "invalid pattern",

0 commit comments

Comments
 (0)