Skip to content

Commit

Permalink
Merge branch 'main' into tests/pattern-test-cases-c
Browse files Browse the repository at this point in the history
  • Loading branch information
kashifkhan0771 authored Nov 7, 2024
2 parents bb9a7d8 + 034ca35 commit 11bafda
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 89 deletions.
50 changes: 36 additions & 14 deletions pkg/detectors/falsepositives.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

var DefaultFalsePositives = []FalsePositive{"example", "xxxxxx", "aaaaaa", "abcde", "00000", "sample", "*****"}
var (
DefaultFalsePositives = map[FalsePositive]struct{}{
"example": {}, "xxxxxx": {}, "aaaaaa": {}, "abcde": {}, "00000": {}, "sample": {}, "*****": {},
}
UuidFalsePositives map[FalsePositive]struct{}
)

type FalsePositive string

Expand All @@ -24,18 +29,21 @@ type CustomFalsePositiveChecker interface {
IsFalsePositive(result Result) (bool, string)
}

//go:embed "badlist.txt"
var badList []byte

//go:embed "words.txt"
var wordList []byte

//go:embed "programmingbooks.txt"
var programmingBookWords []byte

var filter *ahocorasick.Trie
var (
filter *ahocorasick.Trie

//go:embed "fp_badlist.txt"
badList []byte
//go:embed "fp_words.txt"
wordList []byte
//go:embed "fp_programmingbooks.txt"
programmingBookWords []byte
//go:embed "fp_uuids.txt"
uuidList []byte
)

func init() {
// Populate trie.
builder := ahocorasick.NewTrieBuilder()

wordList := bytesToCleanWordList(wordList)
Expand All @@ -47,7 +55,16 @@ func init() {
programmingBookWords := bytesToCleanWordList(programmingBookWords)
builder.AddStrings(programmingBookWords)

uuidList := bytesToCleanWordList(uuidList)
builder.AddStrings(uuidList)

filter = builder.Build()

// Populate custom FalsePositive list
UuidFalsePositives = make(map[FalsePositive]struct{}, len(uuidList))
for _, uuid := range uuidList {
UuidFalsePositives[FalsePositive(uuid)] = struct{}{}
}
}

func GetFalsePositiveCheck(detector Detector) func(Result) (bool, string) {
Expand All @@ -65,15 +82,20 @@ func GetFalsePositiveCheck(detector Detector) func(Result) (bool, string) {
//
// Currently, this includes: english word in key or matches common example patterns.
// Only the secret key material should be passed into this function
func IsKnownFalsePositive(match string, falsePositives []FalsePositive, wordCheck bool) (bool, string) {
func IsKnownFalsePositive(match string, falsePositives map[FalsePositive]struct{}, wordCheck bool) (bool, string) {
if !utf8.ValidString(match) {
return true, "invalid utf8"
}
lower := strings.ToLower(match)
for _, fp := range falsePositives {

if _, exists := falsePositives[FalsePositive(lower)]; exists {
return true, "matches term: " + lower
}

for fp := range falsePositives {
fps := string(fp)
if strings.Contains(lower, fps) {
return true, "matches term: " + fps
return true, "contains term: " + fps
}
}

Expand Down
19 changes: 11 additions & 8 deletions pkg/detectors/falsepositives_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build detectors
// +build detectors

package detectors

import (
Expand Down Expand Up @@ -32,17 +29,23 @@ func (d fakeDetector) Type() detectorspb.DetectorType {
func (f fakeDetector) Description() string { return "" }

func (d customFalsePositiveChecker) IsFalsePositive(result Result) (bool, string) {
return IsKnownFalsePositive(string(result.Raw), []FalsePositive{"a specific magic string"}, false)
return IsKnownFalsePositive(string(result.Raw), map[FalsePositive]struct{}{"a specific magic string": {}}, false)
}

func TestFilterKnownFalsePositives_DefaultLogic(t *testing.T) {
results := []Result{
{Raw: []byte("00000")}, // "default" false positive list
{Raw: []byte("number")}, // from wordlist
{Raw: []byte("hga8adshla3434g")}, // real secret
{Raw: []byte("00000")}, // "default" false positive list
{Raw: []byte("number")}, // from wordlist
// from uuid list
{Raw: []byte("00000000-0000-0000-0000-000000000000")},
{Raw: []byte("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")},
// real secrets
{Raw: []byte("hga8adshla3434g")},
{Raw: []byte("f795f7db-2dfe-4095-96f3-8f8370c735f9")},
}
expected := []Result{
{Raw: []byte("hga8adshla3434g")},
{Raw: []byte("f795f7db-2dfe-4095-96f3-8f8370c735f9")},
}
filtered := FilterKnownFalsePositives(logContext.Background(), fakeDetector{}, results)
assert.ElementsMatch(t, expected, filtered)
Expand All @@ -67,7 +70,7 @@ func TestFilterKnownFalsePositives_CustomLogic(t *testing.T) {
func TestIsFalsePositive(t *testing.T) {
type args struct {
match string
falsePositives []FalsePositive
falsePositives map[FalsePositive]struct{}
useWordlist bool
}
tests := []struct {
Expand Down
96 changes: 67 additions & 29 deletions pkg/detectors/fetchrss/fetchrss.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@ package fetchrss

import (
"context"
regexp "github.com/wasilibs/go-re2"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

regexp "github.com/wasilibs/go-re2"

"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{}
type Scanner struct {
client *http.Client
}

// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

var (
client = common.SaneHttpClient()
defaultClient = common.SaneHttpClient()

// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fetchrss"}) + `\b([0-9A-Za-z.]{40})\b`)
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fetchrss"}) + `\b([a-zA-Z0-9.]{40})\b`)
)

// Keywords are used for efficiently pre-filtering chunks.
Expand All @@ -34,37 +38,26 @@ func (s Scanner) Keywords() []string {
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])
uniqueMatches := make(map[string]struct{})
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
uniqueMatches[match[1]] = struct{}{}
}

for token := range uniqueMatches {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Fetchrss,
Raw: []byte(resMatch),
Raw: []byte(token),
}

if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://fetchrss.com/api/v1/feed/list?auth="+resMatch, nil)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)

if !strings.Contains(body, "Not authorised") {
s1.Verified = true
}
client := s.client
if client == nil {
client = defaultClient
}

verified, verificationErr := verifyToken(ctx, client, token)
s1.Verified = verified
s1.SetVerificationError(verificationErr)
}

results = append(results, s1)
Expand All @@ -73,6 +66,51 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}

func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://fetchrss.com/api/v1/feed/list?auth="+token, nil)
if err != nil {
return false, err
}

res, err := client.Do(req)
if err != nil {
return false, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()

// The API seems to always return a 200 status code.
// See: https://fetchrss.com/developers
if res.StatusCode != http.StatusOK {
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}

var apiRes response
if err := json.NewDecoder(res.Body).Decode(&apiRes); err != nil {
return false, err
}

if apiRes.Success {
// The key is valid.
return true, nil
} else if apiRes.Error.Code == 401 {
// The key is invalid.
return false, nil
} else {
return false, fmt.Errorf("unexpected error: [code=%d, message=%s]", apiRes.Error.Code, apiRes.Error.Message)
}
}

type response struct {
Success bool `json:"success"`
Error struct {
Message string `json:"message"`
Code int `json:"code"`
} `json:"error"`
}

func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Fetchrss
}
Expand Down
File renamed without changes.
File renamed without changes.
37 changes: 37 additions & 0 deletions pkg/detectors/fp_uuids.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
00000000-0000-0000-0000-000000000000
11111111-1111-1111-1111-111111111111
22222222-2222-2222-2222-222222222222
33333333-3333-3333-3333-333333333333
44444444-4444-4444-4444-444444444444
55555555-5555-5555-5555-555555555555
66666666-6666-6666-6666-666666666666
77777777-7777-7777-7777-777777777777
88888888-8888-8888-8888-888888888888
99999999-9999-9999-9999-999999999999
12345678-1234-1234-1234-123456789abc
23456789-2345-2345-2345-23456789abcd
34567890-3456-3456-3456-34567890bcde
45678901-4567-4567-4567-45678901cdef
56789012-5678-5678-5678-56789012def0
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
cccccccc-cccc-cccc-cccc-cccccccccccc
dddddddd-dddd-dddd-dddd-dddddddddddd
eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee
ffffffff-ffff-ffff-ffff-ffffffffffff
deadbeef-dead-beef-dead-beefdeadbeef
cafebabe-cafe-babe-cafe-babecafebabe
badc0ffee-badc-0ffe-badc-0ffeebadc0f
deadface-dead-face-dead-facedeadface
feedface-feed-face-feed-facefeedface
a1b2c3d4-a1b2-c3d4-a1b2-c3d4a1b2c3d4
98765432-9876-5432-9876-543298765432
abcdefab-cdef-abcd-efab-cdefabcdefab
a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0
b0b0b0b0-b0b0-b0b0-b0b0-b0b0b0b0b0b0
c0c0c0c0-c0c0-c0c0-c0c0-c0c0c0c0c0c0
d0d0d0d0-d0d0-d0d0-d0d0-d0d0d0d0d0d0
e0e0e0e0-e0e0-e0e0-e0e0-e0e0e0e0e0e0
f0f0f0f0-f0f0-f0f0-f0f0-f0f0f0f0f0f0
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
-xxxx-xxxx-xxxx-xxxxxxxxxxxx
File renamed without changes.
6 changes: 5 additions & 1 deletion pkg/detectors/ftp/ftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}

var ftpFalsePositives = map[detectors.FalsePositive]struct{}{
detectors.FalsePositive("@ftp.freebsd.org"): {},
}

func (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {
return detectors.IsKnownFalsePositive(string(result.Raw), []detectors.FalsePositive{"@ftp.freebsd.org"}, false)
return detectors.IsKnownFalsePositive(string(result.Raw), ftpFalsePositives, false)
}

func isErrDeterminate(e error) bool {
Expand Down
7 changes: 5 additions & 2 deletions pkg/detectors/github/v1/github_old.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (s Scanner) Keywords() []string {
return []string{"github", "gh", "pat", "token"}
}

var ghFalsePositives = map[detectors.FalsePositive]struct{}{
detectors.FalsePositive("github commit"): {},
}

// FromData will find and optionally verify GitHub 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)
Expand All @@ -74,8 +78,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result

// Note that this false positive check happens **before** verification! I don't know why it's written this way
// but that's why this logic wasn't moved into a CustomFalsePositiveChecker implementation.
specificFPs := []detectors.FalsePositive{"github commit"}
if isFp, _ := detectors.IsKnownFalsePositive(token, specificFPs, false); isFp {
if isFp, _ := detectors.IsKnownFalsePositive(token, ghFalsePositives, false); isFp {
continue
}

Expand Down
Loading

0 comments on commit 11bafda

Please sign in to comment.