Skip to content

Commit 5219c8a

Browse files
Added twitch detector (#542)
* added braintreepayments detector * added twitch detector * revert commit * enhancement
1 parent 2549f2e commit 5219c8a

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed

pkg/detectors/twitch/twitch.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package twitch
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/url"
7+
"regexp"
8+
"strconv"
9+
"strings"
10+
11+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
12+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
13+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
14+
)
15+
16+
type Scanner struct{}
17+
18+
// Ensure the Scanner satisfies the interface at compile time
19+
var _ detectors.Detector = (*Scanner)(nil)
20+
21+
var (
22+
client = common.SaneHttpClient()
23+
24+
//Make sure that your group is surrounded in boundry characters such as below to reduce false positives
25+
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitch"}) + `\b([0-9a-z]{30})\b`)
26+
idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"twitch"}) + `\b([0-9a-z]{30})\b`)
27+
)
28+
29+
// Keywords are used for efficiently pre-filtering chunks.
30+
// Use identifiers in the secret preferably, or the provider name.
31+
func (s Scanner) Keywords() []string {
32+
return []string{"twitch"}
33+
}
34+
35+
// FromData will find and optionally verify Twitch secrets in a given set of bytes.
36+
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
37+
dataStr := string(data)
38+
39+
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
40+
idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
41+
42+
for _, match := range matches {
43+
if len(match) != 2 {
44+
continue
45+
}
46+
resMatch := strings.TrimSpace(match[1])
47+
48+
for _, idMatch := range idMatches {
49+
if len(idMatch) != 2 {
50+
continue
51+
}
52+
53+
resIdMatch := strings.TrimSpace(idMatch[1])
54+
55+
s1 := detectors.Result{
56+
DetectorType: detectorspb.DetectorType_Twitch,
57+
Raw: []byte(resMatch),
58+
}
59+
60+
if verify {
61+
data := url.Values{}
62+
data.Set("client_id", resIdMatch)
63+
data.Set("client_secret", resMatch)
64+
data.Set("grant_type", "client_credentials")
65+
encodedData := data.Encode()
66+
req, err := http.NewRequestWithContext(ctx, "POST", "https://id.twitch.tv/oauth2/token", strings.NewReader(encodedData))
67+
if err != nil {
68+
continue
69+
}
70+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
71+
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
72+
res, err := client.Do(req)
73+
if err == nil {
74+
defer res.Body.Close()
75+
if res.StatusCode >= 200 && res.StatusCode < 300 {
76+
s1.Verified = true
77+
} else {
78+
//This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key
79+
if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
80+
continue
81+
}
82+
}
83+
}
84+
}
85+
86+
results = append(results, s1)
87+
}
88+
}
89+
return detectors.CleanResults(results), nil
90+
}

pkg/detectors/twitch/twitch_test.go

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package twitch
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/kylelemons/godebug/pretty"
10+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
11+
12+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
13+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
14+
)
15+
16+
func TestTwitch_FromChunk(t *testing.T) {
17+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
18+
defer cancel()
19+
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
20+
if err != nil {
21+
t.Fatalf("could not get test secrets from GCP: %s", err)
22+
}
23+
secret := testSecrets.MustGetField("TWITCH")
24+
id := testSecrets.MustGetField("TWITCH_ID")
25+
inactiveSecret := testSecrets.MustGetField("TWITCH_INACTIVE")
26+
27+
type args struct {
28+
ctx context.Context
29+
data []byte
30+
verify bool
31+
}
32+
tests := []struct {
33+
name string
34+
s Scanner
35+
args args
36+
want []detectors.Result
37+
wantErr bool
38+
}{
39+
{
40+
name: "found, verified",
41+
s: Scanner{},
42+
args: args{
43+
ctx: context.Background(),
44+
data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s", secret, id)),
45+
verify: true,
46+
},
47+
want: []detectors.Result{
48+
{
49+
DetectorType: detectorspb.DetectorType_Twitch,
50+
Verified: true,
51+
},
52+
},
53+
wantErr: false,
54+
},
55+
{
56+
name: "found, unverified",
57+
s: Scanner{},
58+
args: args{
59+
ctx: context.Background(),
60+
data: []byte(fmt.Sprintf("You can find a twitch secret %s within twitch %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
61+
verify: true,
62+
},
63+
want: []detectors.Result{
64+
{
65+
DetectorType: detectorspb.DetectorType_Twitch,
66+
Verified: false,
67+
},
68+
},
69+
wantErr: false,
70+
},
71+
{
72+
name: "not found",
73+
s: Scanner{},
74+
args: args{
75+
ctx: context.Background(),
76+
data: []byte("You cannot find the secret within"),
77+
verify: true,
78+
},
79+
want: nil,
80+
wantErr: false,
81+
},
82+
}
83+
for _, tt := range tests {
84+
t.Run(tt.name, func(t *testing.T) {
85+
s := Scanner{}
86+
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
87+
if (err != nil) != tt.wantErr {
88+
t.Errorf("Twitch.FromData() error = %v, wantErr %v", err, tt.wantErr)
89+
return
90+
}
91+
for i := range got {
92+
if len(got[i].Raw) == 0 {
93+
t.Fatalf("no raw secret present: \n %+v", got[i])
94+
}
95+
got[i].Raw = nil
96+
}
97+
if diff := pretty.Compare(got, tt.want); diff != "" {
98+
t.Errorf("Twitch.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
99+
}
100+
})
101+
}
102+
}
103+
104+
func BenchmarkFromData(benchmark *testing.B) {
105+
ctx := context.Background()
106+
s := Scanner{}
107+
for name, data := range detectors.MustGetBenchmarkData() {
108+
benchmark.Run(name, func(b *testing.B) {
109+
for n := 0; n < b.N; n++ {
110+
_, err := s.FromData(ctx, false, data)
111+
if err != nil {
112+
b.Fatal(err)
113+
}
114+
}
115+
})
116+
}
117+
}

0 commit comments

Comments
 (0)