Skip to content

Commit bbff984

Browse files
Added packagecloud scanner (#404)
* added new protos * added new detectors * added packagecloud scanner * updated regex
1 parent 6c1b3c6 commit bbff984

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package packagecloud
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
10+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
11+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
12+
)
13+
14+
type Scanner struct{}
15+
16+
// Ensure the Scanner satisfies the interface at compile time
17+
var _ detectors.Detector = (*Scanner)(nil)
18+
19+
var (
20+
client = common.SaneHttpClient()
21+
22+
//Make sure that your group is surrounded in boundry characters such as below to reduce false positives
23+
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"packagecloud"}) + `\b([0-9a-f]{48})\b`)
24+
)
25+
26+
// Keywords are used for efficiently pre-filtering chunks.
27+
// Use identifiers in the secret preferably, or the provider name.
28+
func (s Scanner) Keywords() []string {
29+
return []string{"packagecloud"}
30+
}
31+
32+
// FromData will find and optionally verify PackageCloud secrets in a given set of bytes.
33+
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
34+
dataStr := string(data)
35+
36+
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
37+
38+
for _, match := range matches {
39+
if len(match) != 2 {
40+
continue
41+
}
42+
resMatch := strings.TrimSpace(match[1])
43+
44+
s1 := detectors.Result{
45+
DetectorType: detectorspb.DetectorType_PackageCloud,
46+
Raw: []byte(resMatch),
47+
}
48+
49+
if verify {
50+
req, err := http.NewRequestWithContext(ctx, "GET", "https://packagecloud.io/api/v1/repos", nil)
51+
if err != nil {
52+
continue
53+
}
54+
req.SetBasicAuth(resMatch, "")
55+
res, err := client.Do(req)
56+
if err == nil {
57+
defer res.Body.Close()
58+
if res.StatusCode >= 200 && res.StatusCode < 300 {
59+
s1.Verified = true
60+
} else {
61+
//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
62+
if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
63+
continue
64+
}
65+
}
66+
}
67+
}
68+
69+
results = append(results, s1)
70+
}
71+
72+
return detectors.CleanResults(results), nil
73+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package packagecloud
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 TestPackageCloud_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("PACKAGECLOUD")
24+
inactiveSecret := testSecrets.MustGetField("PACKAGECLOUD_INACTIVE")
25+
26+
type args struct {
27+
ctx context.Context
28+
data []byte
29+
verify bool
30+
}
31+
tests := []struct {
32+
name string
33+
s Scanner
34+
args args
35+
want []detectors.Result
36+
wantErr bool
37+
}{
38+
{
39+
name: "found, verified",
40+
s: Scanner{},
41+
args: args{
42+
ctx: context.Background(),
43+
data: []byte(fmt.Sprintf("You can find a packagecloud secret %s within", secret)),
44+
verify: true,
45+
},
46+
want: []detectors.Result{
47+
{
48+
DetectorType: detectorspb.DetectorType_PackageCloud,
49+
Verified: true,
50+
},
51+
},
52+
wantErr: false,
53+
},
54+
{
55+
name: "found, unverified",
56+
s: Scanner{},
57+
args: args{
58+
ctx: context.Background(),
59+
data: []byte(fmt.Sprintf("You can find a packagecloud secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
60+
verify: true,
61+
},
62+
want: []detectors.Result{
63+
{
64+
DetectorType: detectorspb.DetectorType_PackageCloud,
65+
Verified: false,
66+
},
67+
},
68+
wantErr: false,
69+
},
70+
{
71+
name: "not found",
72+
s: Scanner{},
73+
args: args{
74+
ctx: context.Background(),
75+
data: []byte("You cannot find the secret within"),
76+
verify: true,
77+
},
78+
want: nil,
79+
wantErr: false,
80+
},
81+
}
82+
for _, tt := range tests {
83+
t.Run(tt.name, func(t *testing.T) {
84+
s := Scanner{}
85+
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
86+
if (err != nil) != tt.wantErr {
87+
t.Errorf("PackageCloud.FromData() error = %v, wantErr %v", err, tt.wantErr)
88+
return
89+
}
90+
for i := range got {
91+
if len(got[i].Raw) == 0 {
92+
t.Fatalf("no raw secret present: \n %+v", got[i])
93+
}
94+
got[i].Raw = nil
95+
}
96+
if diff := pretty.Compare(got, tt.want); diff != "" {
97+
t.Errorf("PackageCloud.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
98+
}
99+
})
100+
}
101+
}
102+
103+
func BenchmarkFromData(benchmark *testing.B) {
104+
ctx := context.Background()
105+
s := Scanner{}
106+
for name, data := range detectors.MustGetBenchmarkData() {
107+
benchmark.Run(name, func(b *testing.B) {
108+
for n := 0; n < b.N; n++ {
109+
_, err := s.FromData(ctx, false, data)
110+
if err != nil {
111+
b.Fatal(err)
112+
}
113+
}
114+
})
115+
}
116+
}

0 commit comments

Comments
 (0)