Compare commits

...

10 Commits

Author SHA1 Message Date
Dustin Decker 843dd7f056 add extra data to github detector 2023-10-18 08:56:25 -07:00
Damanpreet Singh b9f49933b8 Added Support for '-h' Option for Help Documentation (#1901) 2023-10-18 06:57:05 -07:00
Richard Gomez b46fb75c73 feat(git): only generate line numbers > 0 (#1898) 2023-10-18 06:53:58 -07:00
Richard Gomez 6ea3a7da4a fix(github): normalize repo cache (#1897) 2023-10-17 15:07:47 -07:00
Shreyas Sriram 7f534d0bb7 Fix off by one (#1891) 2023-10-17 07:02:27 -07:00
Miccah d4d4d0ec9a Add ShannonEntropy test for an empty string (#1893) 2023-10-16 13:50:28 -07:00
Miccah 03dc7cb68d [chore] Add SourceUnitEnumChunker filesystem tests (#1873)
* [chore] Add SourceUnitEnumChunker filesystem tests

* Ensure reported units are exactly what is expected
2023-10-16 10:42:18 -07:00
Corben Leo 072e1f9dcf Detector-Competition-Fix: Add Personal Access Tokens (API Tokens Depr… (#1871)
* Detector-Competition-Fix: Add Personal Access Tokens (API Tokens Depreciation)

* fix(test): fix test debug msg

* remove print
2023-10-16 08:17:12 -05:00
ahrav 5c721d1a73 [bug] - Don't modify global client var (#1890)
* Create a new client within the verify block

* remove unused var
2023-10-13 12:32:21 -07:00
s.shivasurya 040167178c added cody gateway token detection code (#1883)
* added cody gateway token detection code

* resolved conflict
2023-10-13 09:09:04 -06:00
21 changed files with 646 additions and 34 deletions
+3
View File
@@ -152,6 +152,9 @@ func init() {
cli.Version("trufflehog " + version.BuildVersion)
//Support -h for help
cli.HelpFlag.Short('h')
if len(os.Args) <= 1 && isatty.IsTerminal(os.Stdout.Fd()) {
args := tui.Run()
if len(args) == 0 {
+27 -2
View File
@@ -2,6 +2,7 @@ package airtableapikey
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
@@ -20,8 +21,9 @@ var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
personalPat = regexp.MustCompile(`(\bpat[[:alnum:]]{14}\.[[:alnum:]]{64}\b)`)
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -30,17 +32,31 @@ func (s Scanner) Keywords() []string {
return []string{"airtable"}
}
type response struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
// FromData will find and optionally verify AirtableApiKey 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)
appMatches := appPat.FindAllStringSubmatch(dataStr, -1)
keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
personalKeyMatches := personalPat.FindAllStringSubmatch(dataStr, -1)
if len(keyMatches) == 0 {
keyMatches = personalKeyMatches
}
for _, keyMatch := range keyMatches {
if len(keyMatch) != 2 {
continue
}
keyRes := strings.TrimSpace(keyMatch[1])
for _, appMatch := range appMatches {
@@ -67,6 +83,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 {
var resp response
if err = json.NewDecoder(res.Body).Decode(&resp); err == nil {
// check if the error is due to invalid permissions or model not found
if resp.Error.Type == "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND" {
// The key is verified as it works, but the user must enumerate the tables or permissions for the key.
s1.Verified = true
}
}
} else {
if detectors.IsKnownFalsePositive(keyRes, detectors.DefaultFalsePositives, true) {
continue
@@ -9,9 +9,10 @@ import (
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
@@ -99,8 +100,9 @@ func TestAirtableApiKey_FromChunk(t *testing.T) {
}
got[i].Raw = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("AirtableApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Airtable.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
+7
View File
@@ -74,6 +74,13 @@ func TestStringShannonEntropy(t *testing.T) {
},
want: 0.22228483068568816,
},
{
name: "empty",
args: args{
input: "",
},
want: 0.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+1
View File
@@ -97,6 +97,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.ExtraData["site_admin"] = fmt.Sprintf("%t", userResponse.SiteAdmin)
s1.ExtraData["name"] = userResponse.Name
s1.ExtraData["company"] = userResponse.Company
s1.ExtraData["scopes"] = res.Header.Get("X-OAuth-Scopes")
}
}
}
+45
View File
@@ -28,6 +28,7 @@ func TestGitHub_FromChunk(t *testing.T) {
unverifiedGhu := testSecrets.MustGetField("GITHUB_UNVERIFIED_GHU")
unverifiedGhs := testSecrets.MustGetField("GITHUB_UNVERIFIED_GHS")
unverifiedGhr := testSecrets.MustGetField("GITHUB_UNVERIFIED_GHR")
verifiedGhp := testSecrets.MustGetField("GITHUB_VERIFIED_GHP")
type args struct {
ctx context.Context
@@ -41,6 +42,32 @@ func TestGitHub_FromChunk(t *testing.T) {
want []detectors.Result
wantErr bool
}{
{
name: "found, verified ghp",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a github secret %s within", verifiedGhp)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Github,
Verified: true,
ExtraData: map[string]string{
"account_type": "User",
"company": "",
"name": "",
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
"scopes": "notifications",
"site_admin": "false",
"url": "https://github.com/truffle-sandbox",
"username": "truffle-sandbox",
},
},
},
wantErr: false,
},
{
name: "found, unverified ghp",
s: Scanner{},
@@ -53,6 +80,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
@@ -69,6 +99,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
@@ -85,6 +118,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
@@ -101,6 +137,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
@@ -117,6 +156,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
@@ -133,6 +175,9 @@ func TestGitHub_FromChunk(t *testing.T) {
{
DetectorType: detectorspb.DetectorType_Github,
Verified: false,
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
},
},
wantErr: false,
+12
View File
@@ -38,6 +38,7 @@ type userRes struct {
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
UserURL string `json:"html_url"`
}
// Keywords are used for efficiently pre-filtering chunks.
@@ -91,6 +92,17 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
res.Body.Close()
if err == nil {
s1.Verified = true
if err == nil {
s1.Verified = true
s1.ExtraData["username"] = userResponse.Login
s1.ExtraData["url"] = userResponse.UserURL
s1.ExtraData["account_type"] = userResponse.Type
s1.ExtraData["site_admin"] = fmt.Sprintf("%t", userResponse.SiteAdmin)
s1.ExtraData["name"] = userResponse.Name
s1.ExtraData["company"] = userResponse.Company
s1.ExtraData["scopes"] = res.Header.Get("X-OAuth-Scopes")
}
}
}
}
@@ -0,0 +1,91 @@
package sourcegraphcody
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(slk_[a-f0-9]{64})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"slk_"}
}
// FromData will find and optionally verify Sourcegraphcody 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)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Raw: []byte(resMatch),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
req, err := http.NewRequestWithContext(ctx, "GET", "https://cody-gateway.sourcegraph.com/v1/limits", nil)
req.Header.Add("Authorization", "Bearer "+resMatch)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 || res.StatusCode == 401 {
// 403 when the gateway is not configured to accept the token,
// 401 when the token is invalid.
// The secret is determinately not verified (nothing to do)
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
// 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.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_SourcegraphCody
}
@@ -0,0 +1,162 @@
//go:build detectors
// +build detectors
package sourcegraphcody
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestSourcegraphcody_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("SOURCEGRAPHCODY")
inactiveSecret := testSecrets.MustGetField("SOURCEGRAPHCODY_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, verified but unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Sourcegraphcody.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Sourcegraphcody.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+1 -2
View File
@@ -23,7 +23,6 @@ var (
identifierPat = regexp.MustCompile(`(?i)sid.{0,20}AC[0-9a-f]{32}`) // Should we have this? Seems restrictive.
sidPat = regexp.MustCompile(`\bAC[0-9a-f]{32}\b`)
keyPat = regexp.MustCompile(`\b[0-9a-f]{32}\b`)
client = common.SaneHttpClient()
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -59,7 +58,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
client = s.client
client := s.client
if client == nil {
client = defaultClient
}
+2
View File
@@ -8,6 +8,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforce"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snowflake"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraph"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraphcody"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tailscale"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trufflehogenterprise"
@@ -1552,6 +1553,7 @@ func DefaultDetectors() []detectors.Detector {
web3storage.Scanner{},
&ramp.Scanner{},
&anthropic.Scanner{},
&sourcegraphcody.Scanner{},
}
}
+1 -1
View File
@@ -635,7 +635,7 @@ func FragmentLineOffset(chunk *sources.Chunk, result *detectors.Result) (int64,
if !found {
return 0, false
}
lineNumber := int64(bytes.Count(before, []byte("\n")))
lineNumber := int64(bytes.Count(before, []byte("\n")) + 1)
// If the line contains the ignore tag, we should ignore the result.
endLine := bytes.Index(after, []byte("\n"))
if endLine == -1 {
+6 -6
View File
@@ -32,7 +32,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret here"),
},
expectedLine: 2,
expectedLine: 3,
ignore: true,
},
{
@@ -43,7 +43,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret here"),
},
expectedLine: 2,
expectedLine: 3,
ignore: false,
},
{
@@ -54,7 +54,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret here"),
},
expectedLine: 4,
expectedLine: 5,
ignore: false,
},
{
@@ -65,7 +65,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret\nhere"),
},
expectedLine: 4,
expectedLine: 5,
ignore: false,
},
{
@@ -76,7 +76,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret\nhere"),
},
expectedLine: 3,
expectedLine: 4,
ignore: true,
},
{
@@ -87,7 +87,7 @@ func TestFragmentLineOffset(t *testing.T) {
result: &detectors.Result{
Raw: []byte("secret here"),
},
expectedLine: 3,
expectedLine: 4,
ignore: true,
},
}
+5
View File
@@ -158,6 +158,11 @@ func UpdateLinkLineNumber(ctx context.Context, link string, newLine int64) strin
return link
}
if newLine <= 0 {
// Don't change the link if the line number is 0.
return link
}
switch determineProvider(link) {
case providerBitbucket:
// For Bitbucket, it doesn't support line links (based on the GenerateLink function).
+8
View File
@@ -251,6 +251,14 @@ func TestUpdateLinkLineNumber(t *testing.T) {
},
want: "https://onprem.customdomain.com/org/repo/commit/xyz123#L50",
},
{
name: "Don't include line when it's 0",
args: args{
link: "https://github.com/coinbase/cbpay-js/issues/181",
newLine: int64(0),
},
want: "https://github.com/coinbase/cbpay-js/issues/181",
},
{
name: "Invalid link",
args: args{
+11 -7
View File
@@ -1007,6 +1007,7 @@ const (
DetectorType_Anthropic DetectorType = 933
DetectorType_Ramp DetectorType = 934
DetectorType_Klaviyo DetectorType = 935
DetectorType_SourcegraphCody DetectorType = 936
)
// Enum value maps for DetectorType.
@@ -1944,6 +1945,7 @@ var (
933: "Anthropic",
934: "Ramp",
935: "Klaviyo",
936: "SourcegraphCody",
}
DetectorType_value = map[string]int32{
"Alibaba": 0,
@@ -2878,6 +2880,7 @@ var (
"Anthropic": 933,
"Ramp": 934,
"Klaviyo": 935,
"SourcegraphCody": 936,
}
)
@@ -3256,7 +3259,7 @@ var file_detectors_proto_rawDesc = []byte{
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, 0x2a, 0x90, 0x75, 0x0a, 0x0c, 0x44,
0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x2a, 0xa6, 0x75, 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,
@@ -4193,12 +4196,13 @@ var file_detectors_proto_rawDesc = []byte{
0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x44,
0x62, 0x10, 0xa4, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x6e, 0x74, 0x68, 0x72, 0x6f, 0x70, 0x69,
0x63, 0x10, 0xa5, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61, 0x6d, 0x70, 0x10, 0xa6, 0x07, 0x12,
0x0c, 0x0a, 0x07, 0x4b, 0x6c, 0x61, 0x76, 0x69, 0x79, 0x6f, 0x10, 0xa7, 0x07, 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,
0x0c, 0x0a, 0x07, 0x4b, 0x6c, 0x61, 0x76, 0x69, 0x79, 0x6f, 0x10, 0xa7, 0x07, 0x12, 0x14, 0x0a,
0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x43, 0x6f, 0x64, 0x79,
0x10, 0xa8, 0x07, 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 (
+1 -2
View File
@@ -39,8 +39,7 @@ type Source struct {
// Ensure the Source satisfies the interfaces at compile time
var _ sources.Source = (*Source)(nil)
var _ sources.SourceUnitUnmarshaller = (*Source)(nil)
var _ sources.SourceUnitEnumerator = (*Source)(nil)
var _ sources.SourceUnitChunker = (*Source)(nil)
var _ sources.SourceUnitEnumChunker = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
+195 -10
View File
@@ -15,6 +15,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest"
)
func TestSource_Scan(t *testing.T) {
@@ -84,23 +85,15 @@ func TestSource_Scan(t *testing.T) {
}
func TestScanFile(t *testing.T) {
tmpfile, err := os.CreateTemp("", "example.txt")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
chunkSize := sources.ChunkSize
secretPart1 := "SECRET"
secretPart2 := "SPLIT"
// Split the secret into two parts and pad the rest of the chunk with A's.
data := strings.Repeat("A", chunkSize-len(secretPart1)) + secretPart1 + secretPart2 + strings.Repeat("A", chunkSize-len(secretPart2))
_, err = tmpfile.Write([]byte(data))
assert.Nil(t, err)
err = tmpfile.Close()
tmpfile, cleanup, err := createTempFile("", data)
assert.Nil(t, err)
defer cleanup()
source := &Source{}
chunksChan := make(chan *sources.Chunk, 2)
@@ -120,3 +113,195 @@ func TestScanFile(t *testing.T) {
assert.Contains(t, foundSecret, secretPart1+secretPart2)
}
func TestEnumerate(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup the connection to test enumeration.
units := []string{
"/one", "/two", "/three",
"/path/to/dir/", "/path/to/another/dir/",
}
conn, err := anypb.New(&sourcespb.Filesystem{
Paths: units[0:3],
Directories: units[3:],
})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test enumerate", 0, 0, true, conn, 1)
assert.NoError(t, err)
reporter := sourcestest.TestReporter{}
err = s.Enumerate(ctx, &reporter)
assert.NoError(t, err)
assert.Equal(t, len(units), len(reporter.Units))
assert.Equal(t, 0, len(reporter.UnitErrs))
for _, unit := range reporter.Units {
assert.Contains(t, units, unit.SourceUnitID())
}
for _, unit := range units {
assert.Contains(t, reporter.Units, sources.CommonSourceUnit{ID: unit})
}
}
func TestChunkUnit(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup test file to chunk.
fileContents := "TestChunkUnit"
tmpfile, cleanup, err := createTempFile("", fileContents)
assert.NoError(t, err)
defer cleanup()
tmpdir, cleanup, err := createTempDir("", "foo", "bar", "baz")
assert.NoError(t, err)
defer cleanup()
conn, err := anypb.New(&sourcespb.Filesystem{})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test chunk unit", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Happy path single file.
reporter := sourcestest.TestReporter{}
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpfile.Name(),
}, &reporter)
assert.NoError(t, err)
// Happy path directory.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpdir,
}, &reporter)
assert.NoError(t, err)
// Error path.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: "/file/not/found",
}, &reporter)
assert.NoError(t, err)
assert.Equal(t, 4, len(reporter.Chunks))
assert.Equal(t, 1, len(reporter.ChunkErrs))
dataFound := make(map[string]struct{}, 4)
for _, chunk := range reporter.Chunks {
dataFound[string(chunk.Data)] = struct{}{}
}
assert.Contains(t, dataFound, fileContents)
assert.Contains(t, dataFound, "foo")
assert.Contains(t, dataFound, "bar")
assert.Contains(t, dataFound, "baz")
}
func TestEnumerateReporterErr(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup the connection to test enumeration.
units := []string{
"/one", "/two", "/three",
"/path/to/dir/", "/path/to/another/dir/",
}
conn, err := anypb.New(&sourcespb.Filesystem{
Paths: units[0:3],
Directories: units[3:],
})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test enumerate", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Enumerate should always return an error if the reporter returns an
// error.
reporter := sourcestest.ErrReporter{}
err = s.Enumerate(ctx, &reporter)
assert.Error(t, err)
}
func TestChunkUnitReporterErr(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup test file to chunk.
tmpfile, err := os.CreateTemp("", "example.txt")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
fileContents := []byte("TestChunkUnit")
_, err = tmpfile.Write(fileContents)
assert.NoError(t, err)
assert.NoError(t, tmpfile.Close())
conn, err := anypb.New(&sourcespb.Filesystem{})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test chunk unit", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Happy path. ChunkUnit should always return an error if the reporter
// returns an error.
reporter := sourcestest.ErrReporter{}
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpfile.Name(),
}, &reporter)
assert.Error(t, err)
// Error path. ChunkUnit should always return an error if the reporter
// returns an error.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: "/file/not/found",
}, &reporter)
assert.Error(t, err)
}
// createTempFile is a helper function to create a temporary file in the given
// directory with the provided contents. If dir is "", the operating system's
// temp directory is used.
func createTempFile(dir string, contents string) (*os.File, func(), error) {
tmpfile, err := os.CreateTemp(dir, "trufflehogtest")
if err != nil {
return nil, nil, err
}
if _, err := tmpfile.Write([]byte(contents)); err != nil {
_ = os.Remove(tmpfile.Name())
return nil, nil, err
}
if err := tmpfile.Close(); err != nil {
_ = os.Remove(tmpfile.Name())
return nil, nil, err
}
return tmpfile, func() { _ = os.Remove(tmpfile.Name()) }, nil
}
// createTempDir is a helper function to create a temporary directory in the
// given directory with files containing the provided contents. If dir is "",
// the operating system's temp directory is used.
func createTempDir(dir string, contents ...string) (string, func(), error) {
tmpdir, err := os.MkdirTemp(dir, "trufflehogtest")
if err != nil {
return "", nil, err
}
for _, content := range contents {
if _, _, err := createTempFile(tmpdir, content); err != nil {
_ = os.RemoveAll(tmpdir)
return "", nil, err
}
}
return tmpdir, func() { _ = os.RemoveAll(tmpdir) }, nil
}
+1 -1
View File
@@ -233,7 +233,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
aCtx.Logger().Error(err, "invalid repository", "repo", repo)
continue
}
s.filteredRepoCache.Set(r, r)
s.filteredRepoCache.Set(repo, r)
}
s.includeIssueComments = s.conn.IncludeIssueComments
+61
View File
@@ -0,0 +1,61 @@
package sourcestest
import (
"fmt"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
type reporter interface {
sources.UnitReporter
sources.ChunkReporter
}
var (
_ reporter = (*TestReporter)(nil)
_ reporter = (*ErrReporter)(nil)
)
// TestReporter is a helper struct that implements both UnitReporter and
// ChunkReporter by simply recording the values passed in the methods.
type TestReporter struct {
Units []sources.SourceUnit
UnitErrs []error
Chunks []sources.Chunk
ChunkErrs []error
}
func (t *TestReporter) UnitOk(_ context.Context, unit sources.SourceUnit) error {
t.Units = append(t.Units, unit)
return nil
}
func (t *TestReporter) UnitErr(_ context.Context, err error) error {
t.UnitErrs = append(t.UnitErrs, err)
return nil
}
func (t *TestReporter) ChunkOk(_ context.Context, chunk sources.Chunk) error {
t.Chunks = append(t.Chunks, chunk)
return nil
}
func (t *TestReporter) ChunkErr(_ context.Context, err error) error {
t.ChunkErrs = append(t.ChunkErrs, err)
return nil
}
// ErrReporter implements UnitReporter and ChunkReporter but always returns an
// error.
type ErrReporter struct{}
func (ErrReporter) UnitOk(context.Context, sources.SourceUnit) error {
return fmt.Errorf("ErrReporter: UnitOk error")
}
func (ErrReporter) UnitErr(context.Context, error) error {
return fmt.Errorf("ErrReporter: UnitErr error")
}
func (ErrReporter) ChunkOk(context.Context, sources.Chunk) error {
return fmt.Errorf("ErrReporter: ChunkOk error")
}
func (ErrReporter) ChunkErr(context.Context, error) error {
return fmt.Errorf("ErrReporter: ChunkErr error")
}
+1
View File
@@ -944,6 +944,7 @@ enum DetectorType {
Anthropic = 933;
Ramp = 934;
Klaviyo = 935;
SourcegraphCody = 936;
}
message Result {