From 0932ea224b5f9d4ab7e829989a13fdac7aa0383b Mon Sep 17 00:00:00 2001 From: ahrav Date: Sat, 26 Aug 2023 20:39:50 -0700 Subject: [PATCH 01/19] [chore] - Prevent nil deref panic (#1709) --- pkg/output/legacy_json.go | 2 +- pkg/sources/github/github.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/output/legacy_json.go b/pkg/output/legacy_json.go index 5ea3f1d3..e6423d04 100644 --- a/pkg/output/legacy_json.go +++ b/pkg/output/legacy_json.go @@ -133,7 +133,7 @@ func BranchHeads(repo *gogit.Repository) (map[string]*object.Commit, error) { } headCommit, err := repo.CommitObject(*headHash) if err != nil { - logger.Error(err, "unable to get commit", "commit", headCommit.String()) + logger.Error(err, "unable to get commit", "head_hash", headHash.String()) return nil } branches[branchName] = headCommit diff --git a/pkg/sources/github/github.go b/pkg/sources/github/github.go index de4508e3..30226908 100644 --- a/pkg/sources/github/github.go +++ b/pkg/sources/github/github.go @@ -149,14 +149,16 @@ func (s *Source) newFilteredRepoCache(c cache.Cache, include, exclude []string) for _, ig := range include { g, err := glob.Compile(ig) if err != nil { - s.log.V(1).Info("invalid include glob", "glob", g, "err", err) + s.log.V(1).Info("invalid include glob", "include_value", ig, "err", err) + continue } includeGlobs = append(includeGlobs, g) } for _, eg := range exclude { g, err := glob.Compile(eg) if err != nil { - s.log.V(1).Info("invalid exclude glob", "glob", g, "err", err) + s.log.V(1).Info("invalid exclude glob", "exclude_value", eg, "err", err) + continue } excludeGlobs = append(excludeGlobs, g) } From d1b1ac408ef0bf354ae713e63140164680bb7b82 Mon Sep 17 00:00:00 2001 From: Zachary Rice Date: Mon, 28 Aug 2023 14:21:13 -0500 Subject: [PATCH 02/19] Expired invite link fix (#1713) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc658311..15de5866 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Have questions? Feedback? Jump in slack or discord and hang out with us Join our [Slack Community](https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ) -Join the [Secret Scanning Discord](https://discord.gg/sydS6AHTUP) +Join the [Secret Scanning Discord](https://discord.gg/8Hzbrnkr7E) # :tv: Demo From 66271aed7b01017ce44109dfd5ac56c4b1db00e1 Mon Sep 17 00:00:00 2001 From: Zubair Khan Date: Mon, 28 Aug 2023 21:02:11 -0400 Subject: [PATCH 03/19] add detectors that were missed (#1716) --- pkg/engine/defaults.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/engine/defaults.go b/pkg/engine/defaults.go index 89a8f076..af2dbda9 100644 --- a/pkg/engine/defaults.go +++ b/pkg/engine/defaults.go @@ -2,6 +2,10 @@ package engine import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/envoyapikey" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforce" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snowflake" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trufflehogenterprise" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/abbysale" @@ -287,6 +291,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getresponse" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getsandbox" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github_oauth2" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github_old" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/githubapp" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab" @@ -556,7 +561,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/satismeterwritekey" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/saucelabs" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalewaykey" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github_oauth2" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalr" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapeowl" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperapi" @@ -1532,6 +1536,10 @@ func DefaultDetectors() []detectors.Detector { couchbase.Scanner{}, envoyapikey.Scanner{}, github_oauth2.Scanner{}, + snowflake.Scanner{}, + huggingface.Scanner{}, + trufflehogenterprise.Scanner{}, + salesforce.Scanner{}, } } From c51e8f8af5f4948ef92d5507a08e0228de0bdb63 Mon Sep 17 00:00:00 2001 From: ahrav Date: Mon, 28 Aug 2023 18:08:31 -0700 Subject: [PATCH 04/19] buffer channel. (#1718) --- pkg/sources/chunker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sources/chunker.go b/pkg/sources/chunker.go index 78a26aed..60d5c07d 100644 --- a/pkg/sources/chunker.go +++ b/pkg/sources/chunker.go @@ -20,7 +20,7 @@ const ( // Chunker takes a chunk and splits it into chunks of ChunkSize. func Chunker(originalChunk *Chunk) chan *Chunk { - chunkChan := make(chan *Chunk) + chunkChan := make(chan *Chunk, 1) go func() { defer close(chunkChan) if len(originalChunk.Data) <= TotalChunkSize { From 2b1b1b5ad06e6dd342d4db79e529f02169e06258 Mon Sep 17 00:00:00 2001 From: ahrav Date: Tue, 29 Aug 2023 12:02:30 -0700 Subject: [PATCH 05/19] Add jobID to chunk. (#1721) --- pkg/sources/circleci/circleci.go | 1 + pkg/sources/docker/docker.go | 1 + pkg/sources/filesystem/filesystem.go | 2 ++ pkg/sources/gcs/gcs.go | 1 + pkg/sources/git/git.go | 7 +++++++ pkg/sources/github/github.go | 3 +++ pkg/sources/s3/s3.go | 1 + pkg/sources/syslog/syslog.go | 2 ++ 8 files changed, 18 insertions(+) diff --git a/pkg/sources/circleci/circleci.go b/pkg/sources/circleci/circleci.go index 1fb94c17..2ae7e6b3 100644 --- a/pkg/sources/circleci/circleci.go +++ b/pkg/sources/circleci/circleci.go @@ -238,6 +238,7 @@ func (s *Source) chunkAction(ctx context.Context, proj project, bld build, act a SourceType: s.Type(), SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), Data: removeCircleSha1Line(data.Bytes()), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Circleci{ diff --git a/pkg/sources/docker/docker.go b/pkg/sources/docker/docker.go index 71b57e6e..7d347517 100644 --- a/pkg/sources/docker/docker.go +++ b/pkg/sources/docker/docker.go @@ -174,6 +174,7 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) err SourceType: s.Type(), SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), Data: file.Bytes(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Docker{ diff --git a/pkg/sources/filesystem/filesystem.go b/pkg/sources/filesystem/filesystem.go index 89d47348..49f50400 100644 --- a/pkg/sources/filesystem/filesystem.go +++ b/pkg/sources/filesystem/filesystem.go @@ -161,6 +161,7 @@ func (s *Source) scanFile(ctx context.Context, path string, chunksChan chan *sou SourceType: s.Type(), SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Filesystem{ Filesystem: &source_metadatapb.Filesystem{ @@ -191,6 +192,7 @@ func (s *Source) scanFile(ctx context.Context, path string, chunksChan chan *sou SourceType: s.Type(), SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), Data: data.Bytes(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Filesystem{ diff --git a/pkg/sources/gcs/gcs.go b/pkg/sources/gcs/gcs.go index f2a0bedb..dfb0a40f 100644 --- a/pkg/sources/gcs/gcs.go +++ b/pkg/sources/gcs/gcs.go @@ -324,6 +324,7 @@ func (s *Source) processObject(ctx context.Context, o object) error { chunkSkel := &sources.Chunk{ SourceName: s.name, SourceType: s.Type(), + JobID: s.JobID(), SourceID: s.sourceId, Verify: s.verify, SourceMetadata: &source_metadatapb.MetaData{ diff --git a/pkg/sources/git/git.go b/pkg/sources/git/git.go index 9bed59ca..76301cba 100644 --- a/pkg/sources/git/git.go +++ b/pkg/sources/git/git.go @@ -446,6 +446,7 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string chunkSkel := &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Verify: s.verify, @@ -464,6 +465,7 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string chunksChan <- &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Data: diff.Content.Bytes(), @@ -490,6 +492,7 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, chunksChan <- &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Data: append([]byte{}, newChunkBuffer.Bytes()...), @@ -504,6 +507,7 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, chunksChan <- &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Data: line, @@ -523,6 +527,7 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, chunksChan <- &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Data: append([]byte{}, newChunkBuffer.Bytes()...), @@ -588,6 +593,7 @@ func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, chunkSkel := &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Verify: s.verify, @@ -602,6 +608,7 @@ func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, chunksChan <- &sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, + JobID: s.jobID, SourceType: s.sourceType, SourceMetadata: metadata, Data: diff.Content.Bytes(), diff --git a/pkg/sources/github/github.go b/pkg/sources/github/github.go index 30226908..3b5d1f01 100644 --- a/pkg/sources/github/github.go +++ b/pkg/sources/github/github.go @@ -1225,6 +1225,7 @@ func (s *Source) chunkIssueComments(ctx context.Context, repo, repoPath string, chunk := &sources.Chunk{ SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), SourceType: s.Type(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Github{ @@ -1258,6 +1259,7 @@ func (s *Source) chunkPullRequestComments(ctx context.Context, repo string, comm SourceName: s.name, SourceID: s.SourceID(), SourceType: s.Type(), + JobID: s.JobID(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Github{ Github: &source_metadatapb.Github{ @@ -1289,6 +1291,7 @@ func (s *Source) chunkGistComments(ctx context.Context, gistUrl string, comments SourceName: s.name, SourceID: s.SourceID(), SourceType: s.Type(), + JobID: s.JobID(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_Github{ Github: &source_metadatapb.Github{ diff --git a/pkg/sources/s3/s3.go b/pkg/sources/s3/s3.go index b05832ed..44d62572 100644 --- a/pkg/sources/s3/s3.go +++ b/pkg/sources/s3/s3.go @@ -376,6 +376,7 @@ func (s *Source) pageChunker(ctx context.Context, client *s3.S3, chunksChan chan SourceType: s.Type(), SourceName: s.name, SourceID: s.SourceID(), + JobID: s.JobID(), SourceMetadata: &source_metadatapb.MetaData{ Data: &source_metadatapb.MetaData_S3{ S3: &source_metadatapb.S3{ diff --git a/pkg/sources/syslog/syslog.go b/pkg/sources/syslog/syslog.go index b2ba64fa..d9be9d11 100644 --- a/pkg/sources/syslog/syslog.go +++ b/pkg/sources/syslog/syslog.go @@ -272,6 +272,7 @@ func (s *Source) monitorConnection(ctx context.Context, conn net.Conn, chunksCha SourceName: s.syslog.sourceName, SourceID: s.syslog.sourceID, SourceType: s.syslog.sourceType, + JobID: s.JobID(), SourceMetadata: metadata, Data: input, Verify: s.verify, @@ -313,6 +314,7 @@ func (s *Source) acceptUDPConnections(ctx context.Context, netListener net.Packe chunksChan <- &sources.Chunk{ SourceName: s.syslog.sourceName, SourceID: s.syslog.sourceID, + JobID: s.JobID(), SourceType: s.syslog.sourceType, SourceMetadata: metadata, Data: input, From 7ba880f47a0df1a5edd61210ddcc8adfdd897552 Mon Sep 17 00:00:00 2001 From: Miccah Date: Tue, 29 Aug 2023 12:36:44 -0700 Subject: [PATCH 06/19] Add AvailableCapacity method to SourceManager (#1665) --- pkg/sources/source_manager.go | 22 ++++++++++++++++++++-- pkg/sources/source_manager_test.go | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/pkg/sources/source_manager.go b/pkg/sources/source_manager.go index c5800806..19976275 100644 --- a/pkg/sources/source_manager.go +++ b/pkg/sources/source_manager.go @@ -36,7 +36,10 @@ type SourceManager struct { handles map[handle]sourceInfo handlesLock sync.Mutex // Pool limiting the amount of concurrent sources running. - pool errgroup.Group + pool errgroup.Group + poolLimit int + currentRunningCount int32 + // Max number of units to scan concurrently per source. concurrentUnits int // Run the sources using source unit enumeration / chunking if available. useSourceUnits bool @@ -68,7 +71,10 @@ func WithReportHook(hook JobProgressHook) func(*SourceManager) { // WithConcurrentSources limits the concurrent number of sources a manager can run. func WithConcurrentSources(concurrency int) func(*SourceManager) { - return func(mgr *SourceManager) { mgr.pool.SetLimit(concurrency) } + return func(mgr *SourceManager) { + mgr.pool.SetLimit(concurrency) + mgr.poolLimit = concurrency + } } // WithBufferedOutput sets the size of the buffer used for the Chunks() channel. @@ -167,6 +173,8 @@ func (s *SourceManager) asyncRun(ctx context.Context, handle handle) (JobProgres ctx, cancel := context.WithCancel(ctx) progress := NewJobProgress(jobID, int64(handle), sourceName, WithHooks(s.hooks...), WithCancel(cancel)) s.pool.Go(func() error { + atomic.AddInt32(&s.currentRunningCount, 1) + defer atomic.AddInt32(&s.currentRunningCount, -1) ctx := context.WithValues(ctx, "job_id", jobID, "source_manager_worker_id", common.RandomID(5), @@ -207,6 +215,16 @@ func (s *SourceManager) ScanChunk(chunk *Chunk) { s.outputChunks <- chunk } +// AvailableCapacity returns the number of concurrent jobs the manager can +// accommodate at this time. If there is no limit, -1 is returned. +func (s *SourceManager) AvailableCapacity() int { + if s.poolLimit == 0 { + return -1 + } + runCount := atomic.LoadInt32(&s.currentRunningCount) + return s.poolLimit - int(runCount) +} + // preflightChecks is a helper method to check the Manager or the context isn't // done and that the handle is valid. func (s *SourceManager) preflightChecks(ctx context.Context, handle handle) error { diff --git a/pkg/sources/source_manager_test.go b/pkg/sources/source_manager_test.go index f6a0d523..9a7456b3 100644 --- a/pkg/sources/source_manager_test.go +++ b/pkg/sources/source_manager_test.go @@ -317,3 +317,24 @@ func TestSourceManagerCancelRun(t *testing.T) { assert.Error(t, ref.Snapshot().FatalError()) assert.True(t, errors.Is(ref.Snapshot().FatalError(), returnedErr)) } + +func TestSourceManagerAvailableCapacity(t *testing.T) { + mgr := NewManager(WithConcurrentSources(1337)) + start, end := make(chan struct{}), make(chan struct{}) + handle, err := enrollDummy(mgr, callbackChunker{func(context.Context, chan *Chunk) error { + start <- struct{}{} // Send start signal. + <-end // Wait for end signal. + return nil + }}) + assert.NoError(t, err) + + assert.Equal(t, 1337, mgr.AvailableCapacity()) + ref, err := mgr.ScheduleRun(context.Background(), handle) + assert.NoError(t, err) + + <-start // Wait for start signal. + assert.Equal(t, 1336, mgr.AvailableCapacity()) + end <- struct{}{} // Send end signal. + <-ref.Done() // Wait for the job to finish. + assert.Equal(t, 1337, mgr.AvailableCapacity()) +} From 4dc5eb7912c64d230fc59f8d5e30d021f06769e9 Mon Sep 17 00:00:00 2001 From: ahrav Date: Tue, 29 Aug 2023 17:31:40 -0700 Subject: [PATCH 07/19] Optimize read to max (#1714) * optimize the ReadToMax. * add comment. * remove dumb comment. * update comment. * fix test. * lint. * address comments. * use limit reader. * update equality check. * update test.' * use custom limit reader. * address comments. * revert fun. --- pkg/handlers/archive.go | 47 ++++++++++++++++---------------- pkg/handlers/archive_test.go | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/pkg/handlers/archive.go b/pkg/handlers/archive.go index f743c2c9..4551c305 100644 --- a/pkg/handlers/archive.go +++ b/pkg/handlers/archive.go @@ -29,6 +29,8 @@ var ( maxDepth = 5 maxSize = 250 * 1024 * 1024 // 20MB maxTimeout = time.Duration(30) * time.Second + + defaultBufferSize = 512 ) // Ensure the Archive satisfies the interfaces at compile time. @@ -62,7 +64,7 @@ func SetArchiveMaxTimeout(timeout time.Duration) { // FromFile extracts the files from an archive. func (a *Archive) FromFile(originalCtx context.Context, data io.Reader) chan []byte { - archiveChan := make(chan []byte, 512) + archiveChan := make(chan []byte, defaultBufferSize) go func() { ctx, cancel := context.WithTimeout(originalCtx, maxTimeout) logger := logContext.AddLogger(ctx).Logger() @@ -183,29 +185,28 @@ func (a *Archive) ReadToMax(ctx context.Context, reader io.Reader) (data []byte, logger.Error(err, "Panic occurred when reading archive") } }() - fileContent := bytes.Buffer{} - logger.V(5).Info("Remaining buffer capacity", "bytes", maxSize-a.size) - for i := 0; i <= maxSize/512; i++ { - if common.IsDone(ctx) { - return nil, ctx.Err() - } - fileChunk := make([]byte, 512) - bRead, err := reader.Read(fileChunk) - if err != nil && !errors.Is(err, io.EOF) { - return []byte{}, err - } - a.size += bRead - if len(fileChunk) > 0 { - fileContent.Write(fileChunk[0:bRead]) - } - if bRead < 512 { - return fileContent.Bytes(), nil - } - if a.size >= maxSize && bRead == 512 { - logger.V(2).Info("Max archive size reached.") - return fileContent.Bytes(), nil - } + + if common.IsDone(ctx) { + return nil, ctx.Err() } + + var fileContent bytes.Buffer + // Create a limited reader to ensure we don't read more than the max size. + lr := io.LimitReader(reader, int64(maxSize)) + + // Using io.CopyBuffer for performance advantages. Though buf is mandatory + // for the method, due to the internal implementation of io.CopyBuffer, when + // *bytes.Buffer implements io.WriterTo or io.ReaderFrom, the provided buf + // is simply ignored. Thus, we can pass nil for the buf parameter. + _, err = io.CopyBuffer(&fileContent, lr, nil) + if err != nil && !errors.Is(err, io.EOF) { + return nil, err + } + + if fileContent.Len() == maxSize { + logger.V(2).Info("Max archive size reached.") + } + return fileContent.Bytes(), nil } diff --git a/pkg/handlers/archive_test.go b/pkg/handlers/archive_test.go index d0b0ed7c..c6fbcf90 100644 --- a/pkg/handlers/archive_test.go +++ b/pkg/handlers/archive_test.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "context" "io" "net/http" @@ -126,6 +127,58 @@ func TestHandleFile(t *testing.T) { assert.Equal(t, 1, len(ch)) } +func TestReadToMax(t *testing.T) { + tests := []struct { + name string + input []byte + expected []byte + }{ + { + name: "read full content within maxSize", + input: []byte("abcdefg"), + expected: []byte("abcdefg"), + }, + { + name: "read content larger than maxSize", + input: make([]byte, maxSize+10), // this creates a byte slice 10 bytes larger than maxSize + expected: make([]byte, maxSize), + }, + { + name: "empty input", + input: []byte(""), + expected: []byte(""), + }, + } + + a := &Archive{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := bytes.NewReader(tt.input) + output, err := a.ReadToMax(context.Background(), reader) + assert.Nil(t, err) + + assert.Equal(t, tt.expected, output) + }) + } +} + +func BenchmarkReadToMax(b *testing.B) { + data := bytes.Repeat([]byte("a"), 1024*1000) // 1MB of data. + reader := bytes.NewReader(data) + a := &Archive{} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StartTimer() + _, _ = a.ReadToMax(context.Background(), reader) + b.StopTimer() + + _, _ = reader.Seek(0, 0) // Reset the reader position. + a.size = 0 // Reset archive size. + } +} + func TestExtractDebContent(t *testing.T) { // Open the sample .deb file from the testdata folder. file, err := os.Open("testdata/test.deb") From d6afca682b411b2ce3cf8b6548f57b57facb66af Mon Sep 17 00:00:00 2001 From: ahrav Date: Wed, 30 Aug 2023 07:17:38 -0700 Subject: [PATCH 08/19] remove fmt.Print (#1727) --- pkg/detectors/salesforce/salesforce.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/detectors/salesforce/salesforce.go b/pkg/detectors/salesforce/salesforce.go index 872e3c83..0623621d 100644 --- a/pkg/detectors/salesforce/salesforce.go +++ b/pkg/detectors/salesforce/salesforce.go @@ -43,8 +43,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result instanceMatches := instancePat.FindAllStringSubmatch(dataStr, -1) tokenMatches := accessTokenPat.FindAllStringSubmatch(dataStr, -1) - fmt.Printf("instanceMatches: %v\n", instanceMatches) - for _, instance := range instanceMatches { if len(instance) != 1 { continue From c77c1172c8b011a90f445bf98d141f59500d3ed3 Mon Sep 17 00:00:00 2001 From: Miccah Date: Wed, 30 Aug 2023 07:57:45 -0700 Subject: [PATCH 09/19] Add the 'Cause' family of functions to the context wrapper library (#1725) Go 1.20 introduced `WithCancelCause`, `WithTimeoutCause`, and `WithDeadlineCause` to allow adding a reason to context cancellations. Adding it to our wrapper will allow us to use these features. --- pkg/context/context.go | 42 +++++++++++++++++++++++++++++++++++-- pkg/context/context_test.go | 8 +++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pkg/context/context.go b/pkg/context/context.go index 1819f616..060a3ed4 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -26,9 +26,10 @@ type Context interface { Logger() logr.Logger } -// CancelFunc is a type alias to context.CancelFunc to allow use as if they are -// the same types. +// CancelFunc and CancelCauseFunc are type aliases to allow use as if they are +// the same types as the standard library variants. type CancelFunc = context.CancelFunc +type CancelCauseFunc = context.CancelCauseFunc // logCtx implements Context. type logCtx struct { @@ -68,6 +69,16 @@ func WithCancel(parent Context) (Context, context.CancelFunc) { return lCtx, cancel } +// WithCancelCause returns context.WithCancelCause with the log object propagated. +func WithCancelCause(parent Context) (Context, context.CancelCauseFunc) { + ctx, cancel := context.WithCancelCause(parent) + lCtx := logCtx{ + log: parent.Logger(), + Context: ctx, + } + return lCtx, cancel +} + // WithDeadline returns context.WithDeadline with the log object propagated and // the deadline added to the structured log values. func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) { @@ -79,6 +90,17 @@ func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) { return lCtx, cancel } +// WithDeadlineCause returns context.WithDeadlineCause with the log object +// propagated and the deadline added to the structured log values. +func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, context.CancelFunc) { + ctx, cancel := context.WithDeadlineCause(parent, d, cause) + lCtx := logCtx{ + log: parent.Logger().WithValues("deadline", d), + Context: ctx, + } + return lCtx, cancel +} + // WithTimeout returns context.WithTimeout with the log object propagated and // the timeout added to the structured log values. func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) { @@ -90,6 +112,22 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, context.Cancel return lCtx, cancel } +// WithTimeoutCause returns context.WithTimeoutCause with the log object +// propagated and the timeout added to the structured log values. +func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, context.CancelFunc) { + ctx, cancel := context.WithTimeoutCause(parent, timeout, cause) + lCtx := logCtx{ + log: parent.Logger().WithValues("timeout", timeout), + Context: ctx, + } + return lCtx, cancel +} + +// Cause returns the context.Cause of the context. +func Cause(ctx context.Context) error { + return context.Cause(ctx) +} + // WithValue returns context.WithValue with the log object propagated and // the value added to the structured log values (if the key is a string). func WithValue(parent Context, key, val any) Context { diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index e6e8d9d0..d735727e 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -3,6 +3,7 @@ package context import ( "bytes" "context" + "fmt" "strings" "testing" "time" @@ -177,3 +178,10 @@ func TestRace(t *testing.T) { cancel() _ = ctx.Err() } + +func TestCause(t *testing.T) { + ctx, cancel := WithCancelCause(Background()) + err := fmt.Errorf("oh no") + cancel(err) + assert.Equal(t, err, Cause(ctx)) +} From 522b2fab295917222fdce414dbc1e5afbbb9f644 Mon Sep 17 00:00:00 2001 From: Miccah Date: Wed, 30 Aug 2023 12:00:44 -0700 Subject: [PATCH 10/19] Add a cancel cause to job cancellation (#1728) --- pkg/sources/job_progress.go | 8 ++++---- pkg/sources/source_manager.go | 10 ++++++++-- pkg/sources/source_manager_test.go | 4 +++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/sources/job_progress.go b/pkg/sources/job_progress.go index 9ed3b588..4acbdd3c 100644 --- a/pkg/sources/job_progress.go +++ b/pkg/sources/job_progress.go @@ -71,11 +71,11 @@ func (r *JobProgressRef) Done() <-chan struct{} { // CancelRun requests that the job this is referencing is cancelled and stops // running. This method will have no effect if the job does not allow // cancellation. -func (r *JobProgressRef) CancelRun() { +func (r *JobProgressRef) CancelRun(cause error) { if r.jobProgress == nil || r.jobProgress.jobCancel == nil { return } - r.jobProgress.jobCancel() + r.jobProgress.jobCancel(cause) } // Fatal is a wrapper around error to differentiate non-fatal errors from fatal @@ -108,7 +108,7 @@ type JobProgress struct { ctx context.Context cancel context.CancelFunc // Requests to cancel the job. - jobCancel context.CancelFunc + jobCancel context.CancelCauseFunc // Metrics. metrics JobProgressMetrics metricsLock sync.Mutex @@ -150,7 +150,7 @@ func WithHooks(hooks ...JobProgressHook) func(*JobProgress) { } // WithCancel allows cancelling the job by the JobProgressRef. -func WithCancel(cancel context.CancelFunc) func(*JobProgress) { +func WithCancel(cancel context.CancelCauseFunc) func(*JobProgress) { return func(jp *JobProgress) { jp.jobCancel = cancel } } diff --git a/pkg/sources/source_manager.go b/pkg/sources/source_manager.go index 19976275..383b8feb 100644 --- a/pkg/sources/source_manager.go +++ b/pkg/sources/source_manager.go @@ -170,7 +170,7 @@ func (s *SourceManager) asyncRun(ctx context.Context, handle handle) (JobProgres return JobProgressRef{SourceID: int64(handle), SourceName: sourceName}, err } // Create a JobProgress object for tracking progress. - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancelCause(ctx) progress := NewJobProgress(jobID, int64(handle), sourceName, WithHooks(s.hooks...), WithCancel(cancel)) s.pool.Go(func() error { atomic.AddInt32(&s.currentRunningCount, 1) @@ -180,7 +180,7 @@ func (s *SourceManager) asyncRun(ctx context.Context, handle handle) (JobProgres "source_manager_worker_id", common.RandomID(5), ) defer common.Recover(ctx) - defer cancel() + defer cancel(nil) return s.run(ctx, handle, jobID, progress) }) return progress.Ref(), nil @@ -247,6 +247,12 @@ func (s *SourceManager) run(ctx context.Context, handle handle, jobID int64, rep report.Start(time.Now()) defer func() { report.End(time.Now()) }() + defer func() { + if err := context.Cause(ctx); err != nil { + report.ReportError(Fatal{err}) + } + }() + // Initialize the source. sourceInfo, ok := s.getSourceInfo(handle) if !ok { diff --git a/pkg/sources/source_manager_test.go b/pkg/sources/source_manager_test.go index 9a7456b3..9a196a89 100644 --- a/pkg/sources/source_manager_test.go +++ b/pkg/sources/source_manager_test.go @@ -312,10 +312,12 @@ func TestSourceManagerCancelRun(t *testing.T) { ref, err := mgr.ScheduleRun(context.Background(), handle) assert.NoError(t, err) - ref.CancelRun() + cancelErr := fmt.Errorf("abort! abort!") + ref.CancelRun(cancelErr) <-ref.Done() assert.Error(t, ref.Snapshot().FatalError()) assert.True(t, errors.Is(ref.Snapshot().FatalError(), returnedErr)) + assert.True(t, errors.Is(ref.Snapshot().FatalErrors(), cancelErr)) } func TestSourceManagerAvailableCapacity(t *testing.T) { From a2c0abbfd6bcffe54daee469d3b13ddc0e0e60a4 Mon Sep 17 00:00:00 2001 From: Cody Rose Date: Wed, 30 Aug 2023 17:49:37 -0400 Subject: [PATCH 11/19] Unify S3 client creation logic (#1657) This PR unifies some code paths within the S3 source. This is being done to better support a future implementation of S3 source validation; less code that runs means less code to validate. The logical change is to move the handling of "role-less" operation down the call tree, which allows for a single code path for more of the S3 code. This PR also fixes a bug that would occur in the (rare) case that the source couldn't create a regional S3 client. Before, an error would be logged, but it would be followed by a panic. Now the bucket in question is skipped. --- pkg/sources/s3/s3.go | 102 ++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 69 deletions(-) diff --git a/pkg/sources/s3/s3.go b/pkg/sources/s3/s3.go index 44d62572..c6eef466 100644 --- a/pkg/sources/s3/s3.go +++ b/pkg/sources/s3/s3.go @@ -104,22 +104,34 @@ func (s *Source) setMaxObjectSize(maxObjectSize int64) { } } -func (s *Source) newClient(region string) (*s3.S3, error) { +func (s *Source) newClient(region, roleArn string) (*s3.S3, error) { cfg := aws.NewConfig() cfg.CredentialsChainVerboseErrors = aws.Bool(true) cfg.Region = aws.String(region) - switch cred := s.conn.GetCredential().(type) { - case *sourcespb.S3_SessionToken: - cfg.Credentials = credentials.NewStaticCredentials(cred.SessionToken.Key, cred.SessionToken.Secret, cred.SessionToken.SessionToken) - case *sourcespb.S3_AccessKey: - cfg.Credentials = credentials.NewStaticCredentials(cred.AccessKey.Key, cred.AccessKey.Secret, "") - case *sourcespb.S3_Unauthenticated: - cfg.Credentials = credentials.AnonymousCredentials - case *sourcespb.S3_CloudEnvironment: - // Nothing needs to be done! - default: - return nil, errors.Errorf("invalid configuration given for %s source", s.name) + if roleArn == "" { + switch cred := s.conn.GetCredential().(type) { + case *sourcespb.S3_SessionToken: + cfg.Credentials = credentials.NewStaticCredentials(cred.SessionToken.Key, cred.SessionToken.Secret, cred.SessionToken.SessionToken) + case *sourcespb.S3_AccessKey: + cfg.Credentials = credentials.NewStaticCredentials(cred.AccessKey.Key, cred.AccessKey.Secret, "") + case *sourcespb.S3_Unauthenticated: + cfg.Credentials = credentials.AnonymousCredentials + case *sourcespb.S3_CloudEnvironment: + // Nothing needs to be done! + default: + return nil, errors.Errorf("invalid configuration given for %s source", s.name) + } + } else { + sess, err := session.NewSession(cfg) + if err != nil { + return nil, err + } + + stsClient := sts.New(sess) + cfg.Credentials = stscreds.NewCredentialsWithClient(stsClient, roleArn, func(p *stscreds.AssumeRoleProvider) { + p.RoleSessionName = "trufflehog" + }) } sess, err := session.NewSessionWithOptions(session.Options{ @@ -133,40 +145,6 @@ func (s *Source) newClient(region string) (*s3.S3, error) { return s3.New(sess), nil } -// Separate role assumption functionality into a different newClient function -func (s *Source) newRoleClient(region string, roleArn string) (*s3.S3, error) { - - cfg := aws.NewConfig() - cfg.CredentialsChainVerboseErrors = aws.Bool(true) - cfg.Region = aws.String(region) - - var baseCredentials *credentials.Credentials - - sess, err := session.NewSession(cfg) - if err != nil { - return nil, err - } - - stsClient := sts.New(sess) - baseCredentials = stscreds.NewCredentialsWithClient(stsClient, roleArn, func(p *stscreds.AssumeRoleProvider) { - p.RoleSessionName = "trufflehog" - }) - - cfg.Credentials = baseCredentials - - sess, err = session.NewSessionWithOptions(session.Options{ - SharedConfigState: session.SharedConfigEnable, - Config: *cfg, - }) - - if err != nil { - return nil, err - } - - // Create and return the S3 client with the final configuration - return s3.New(sess), nil -} - // IAM identity needs s3:ListBuckets permission func (s *Source) getBucketsToScan(client *s3.S3) ([]string, error) { if len(s.conn.Buckets) > 0 { @@ -204,13 +182,10 @@ func (s *Source) scanBuckets(ctx context.Context, client *s3.S3, role string, bu var regionalClient *s3.S3 if region != defaultAWSRegion { - if role != "" { - regionalClient, err = s.newRoleClient(region, role) - } else { - regionalClient, err = s.newClient(region) - } + regionalClient, err = s.newClient(region, role) if err != nil { s.log.Error(err, "could not make regional s3 client") + continue } } else { regionalClient = client @@ -238,24 +213,13 @@ func (s *Source) scanBuckets(ctx context.Context, client *s3.S3, role string, bu func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) error { const defaultAWSRegion = "us-east-1" - if len(s.conn.Roles) > 0 { - for _, role := range s.conn.Roles { - client, err := s.newRoleClient(defaultAWSRegion, role) - if err != nil { - return errors.WrapPrefix(err, "could not create s3 client", 0) - } + roles := s.conn.Roles + if len(roles) == 0 { + roles = []string{""} + } - bucketsToScan, err := s.getBucketsToScan(client) - if err != nil { - return err - } - - if err := s.scanBuckets(ctx, client, role, bucketsToScan, chunksChan); err != nil { - return err - } - } - } else { - client, err := s.newClient(defaultAWSRegion) + for _, role := range roles { + client, err := s.newClient(defaultAWSRegion, role) if err != nil { return errors.WrapPrefix(err, "could not create s3 client", 0) } @@ -265,7 +229,7 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk) err return err } - if err := s.scanBuckets(ctx, client, "", bucketsToScan, chunksChan); err != nil { + if err := s.scanBuckets(ctx, client, role, bucketsToScan, chunksChan); err != nil { return err } } From 07702ea06d1720dd71c5db15b2e9ed5f4cf3c028 Mon Sep 17 00:00:00 2001 From: Zubair Khan Date: Wed, 30 Aug 2023 15:18:17 -0700 Subject: [PATCH 12/19] update slack webhook with tri-state verification (#1724) * add tristate basics * update test * cleanup * fix leading space * fix accidental comment * save changes * update secret in gsm, fix bug * fix linter issue * cleanup * use defaultClient --- pkg/detectors/slackwebhook/slackwebhook.go | 25 +++++-- .../slackwebhook/slackwebhook_test.go | 69 +++++++++++++++---- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/pkg/detectors/slackwebhook/slackwebhook.go b/pkg/detectors/slackwebhook/slackwebhook.go index 6a4a9d41..2f8fe6ab 100644 --- a/pkg/detectors/slackwebhook/slackwebhook.go +++ b/pkg/detectors/slackwebhook/slackwebhook.go @@ -2,6 +2,7 @@ package slackwebhook import ( "context" + "fmt" "io" "net/http" "regexp" @@ -12,14 +13,15 @@ import ( "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(`(https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]{23,25})`) ) @@ -48,6 +50,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { + + client := s.client + if client == nil { + client = defaultClient + } + payload := strings.NewReader(`{"text": ""}`) req, err := http.NewRequestWithContext(ctx, "POST", resMatch, payload) if err != nil { @@ -62,9 +70,18 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result continue } body := string(bodyBytes) - if (res.StatusCode >= 200 && res.StatusCode < 300) || (res.StatusCode == 400 && (strings.Contains(body, "no_text") || strings.Contains(body, "missing_text"))) { + + defer res.Body.Close() + + if res.StatusCode >= 200 && res.StatusCode < 300 || (res.StatusCode == 400 && (strings.Contains(body, "no_text") || strings.Contains(body, "missing_text"))) { s1.Verified = true + } else if res.StatusCode == 401 || res.StatusCode == 403 { + // 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 } } diff --git a/pkg/detectors/slackwebhook/slackwebhook_test.go b/pkg/detectors/slackwebhook/slackwebhook_test.go index dd9b35f4..2d7e80a3 100644 --- a/pkg/detectors/slackwebhook/slackwebhook_test.go +++ b/pkg/detectors/slackwebhook/slackwebhook_test.go @@ -6,10 +6,11 @@ package slackwebhook import ( "context" "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "testing" "time" - "github.com/kylelemons/godebug/pretty" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" @@ -32,11 +33,12 @@ func TestSlackWebhook_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", @@ -52,7 +54,8 @@ func TestSlackWebhook_FromChunk(t *testing.T) { Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", @@ -68,7 +71,8 @@ func TestSlackWebhook_FromChunk(t *testing.T) { Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -78,14 +82,48 @@ func TestSlackWebhook_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + 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 slackwebhook secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SlackWebhook, + 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 slackwebhook secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SlackWebhook, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { t.Errorf("SlackWebhook.FromData() error = %v, wantErr %v", err, tt.wantErr) return @@ -94,9 +132,12 @@ func TestSlackWebhook_FromChunk(t *testing.T) { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("SlackWebhook.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) From 4aa91337e5556f79bd944ccee5c0b05d9839c4e3 Mon Sep 17 00:00:00 2001 From: Miccah Date: Thu, 31 Aug 2023 08:46:43 -0700 Subject: [PATCH 13/19] Update to Go 1.21 (#1733) * Update to Go 1.21 * go mod tidy --- go.mod | 6 +++--- go.sum | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0706d918..72d53d8e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/trufflesecurity/trufflehog/v3 -go 1.20 +go 1.21 replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.1.7-custom5 @@ -50,6 +50,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/jpillora/overseer v1.1.6 github.com/kylelemons/godebug v1.1.0 + github.com/launchdarkly/go-server-sdk/v6 v6.1.0 github.com/lib/pq v1.10.9 github.com/lrstanley/bubblezone v0.0.0-20221222153816-e95291e2243e github.com/mattn/go-isatty v0.0.18 @@ -170,8 +171,8 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -192,7 +193,6 @@ require ( github.com/launchdarkly/go-sdk-events/v2 v2.0.1 // indirect github.com/launchdarkly/go-semver v1.0.2 // indirect github.com/launchdarkly/go-server-sdk-evaluation/v2 v2.0.2 // indirect - github.com/launchdarkly/go-server-sdk/v6 v6.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 587347d7..c9fdb241 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= @@ -65,6 +66,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g= github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -93,12 +95,14 @@ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1L github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg= github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.44.83 h1:7+Rtc2Eio6EKUNoZeMV/IVxzVrY5oBQcNPtCcgIHYJA= @@ -237,6 +241,7 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -262,6 +267,7 @@ github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluW github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -271,6 +277,7 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -366,6 +373,7 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -427,6 +435,7 @@ github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc= github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA= github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -445,6 +454,7 @@ github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -467,7 +477,10 @@ github.com/launchdarkly/go-server-sdk-evaluation/v2 v2.0.2 h1:PAM0GvE0nIUBeOkjdi github.com/launchdarkly/go-server-sdk-evaluation/v2 v2.0.2/go.mod h1:Mztipcz+7ZMatXVun3k/IfPa8IOgUnAqiZawtFh2MRg= github.com/launchdarkly/go-server-sdk/v6 v6.1.0 h1:pp/VBIon5JNbtSw7ih7I5MXN9mXHfo6T5xikKVy52dI= github.com/launchdarkly/go-server-sdk/v6 v6.1.0/go.mod h1:3TfS6vNsNksPT4LGeGuGKtT3zTjTbbOsCUK3nrj1neA= +github.com/launchdarkly/go-test-helpers/v2 v2.2.0 h1:L3kGILP/6ewikhzhdNkHy1b5y4zs50LueWenVF0sBbs= github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= +github.com/launchdarkly/go-test-helpers/v3 v3.0.2 h1:rh0085g1rVJM5qIukdaQ8z1XTWZztbJ49vRZuveqiuU= +github.com/launchdarkly/go-test-helpers/v3 v3.0.2/go.mod h1:u2ZvJlc/DDJTFrshWW50tWMZHLVYXofuSHUfTU/eIwM= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lrstanley/bubblezone v0.0.0-20221222153816-e95291e2243e h1:XoxHx8K6ZKoMtjzWOMDuM69LCdjDDsTOtTfWGrT/fns= @@ -548,6 +561,7 @@ github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChl github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= @@ -626,6 +640,7 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/xanzy/go-gitlab v0.88.0 h1:9GHBrxyCUNZZNuAsbJ1NbEH6XAYsKyTn6NfE0wYO5SY= github.com/xanzy/go-gitlab v0.88.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= @@ -649,6 +664,7 @@ github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGj github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= @@ -885,6 +901,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -980,6 +997,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 6695cf1dce7455b037d499da6d3d985caece2818 Mon Sep 17 00:00:00 2001 From: "s.shivasurya" Date: Thu, 31 Aug 2023 11:47:13 -0400 Subject: [PATCH 14/19] added sourcegraph token verification detection (#1730) --- pkg/detectors/sourcegraph/sourcegraph.go | 94 ++++++++++ pkg/detectors/sourcegraph/sourcegraph_test.go | 161 ++++++++++++++++++ pkg/engine/defaults.go | 2 + pkg/pb/detectorspb/detectors.pb.go | 17 +- proto/detectors.proto | 1 + 5 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 pkg/detectors/sourcegraph/sourcegraph.go create mode 100644 pkg/detectors/sourcegraph/sourcegraph_test.go diff --git a/pkg/detectors/sourcegraph/sourcegraph.go b/pkg/detectors/sourcegraph/sourcegraph.go new file mode 100644 index 00000000..dda15ec0 --- /dev/null +++ b/pkg/detectors/sourcegraph/sourcegraph.go @@ -0,0 +1,94 @@ +package sourcegraph + +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(detectors.PrefixRegex([]string{"sourcegraph", "sg_token"}) + `\b(sgp_[a-z0-9]{40})\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{"sourcegraph", "sg_token"} +} + +// FromData will find and optionally verify Sourcegraph 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_Sourcegraph, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + payload := strings.NewReader("{\"query\": \"query { currentUser { username } }\"}") + + req, err := http.NewRequestWithContext(ctx, "POST", "https://sourcegraph.com/.api/graphql", payload) + req.Header.Add("Authorization", "token "+resMatch) + req.Header.Add("Content-Type", "application/json") + + 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 == 401 { + // 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_Sourcegraph +} diff --git a/pkg/detectors/sourcegraph/sourcegraph_test.go b/pkg/detectors/sourcegraph/sourcegraph_test.go new file mode 100644 index 00000000..d95181c5 --- /dev/null +++ b/pkg/detectors/sourcegraph/sourcegraph_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package sourcegraph + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestSourcegraph_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("SOURCEGRAPH") + inactiveSecret := testSecrets.MustGetField("SOURCEGRAPH_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 sourcegraph secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Sourcegraph, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a sourcegraph 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_Sourcegraph, + 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 sourcegraph secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Sourcegraph, + 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 sourcegraph secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Sourcegraph, + 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("Sourcegraph.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("Sourcegraph.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) + } + } + }) + } +} diff --git a/pkg/engine/defaults.go b/pkg/engine/defaults.go index af2dbda9..f189b432 100644 --- a/pkg/engine/defaults.go +++ b/pkg/engine/defaults.go @@ -5,6 +5,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface" "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/trufflehogenterprise" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" @@ -1540,6 +1541,7 @@ func DefaultDetectors() []detectors.Detector { huggingface.Scanner{}, trufflehogenterprise.Scanner{}, salesforce.Scanner{}, + sourcegraph.Scanner{}, } } diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index a7893dda..ce4971dd 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -999,6 +999,7 @@ const ( DetectorType_Salesforce DetectorType = 925 DetectorType_HuggingFace DetectorType = 926 DetectorType_Snowflake DetectorType = 927 + DetectorType_Sourcegraph DetectorType = 928 ) // Enum value maps for DetectorType. @@ -1928,6 +1929,7 @@ var ( 925: "Salesforce", 926: "HuggingFace", 927: "Snowflake", + 928: "Sourcegraph", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -2854,6 +2856,7 @@ var ( "Salesforce": 925, "HuggingFace": 926, "Snowflake": 927, + "Sourcegraph": 928, } ) @@ -3232,7 +3235,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, 0x8c, 0x74, 0x0a, 0x0c, 0x44, + 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x2a, 0x9e, 0x74, 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, @@ -4161,11 +4164,13 @@ var file_detectors_proto_rawDesc = []byte{ 0x74, 0x68, 0x32, 0x10, 0x9c, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x10, 0x9d, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x65, 0x10, 0x9e, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x6e, 0x6f, - 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x10, 0x9f, 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, + 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x10, 0x9f, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x10, 0xa0, 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 ( diff --git a/proto/detectors.proto b/proto/detectors.proto index a5c9de93..5f4e293a 100644 --- a/proto/detectors.proto +++ b/proto/detectors.proto @@ -936,6 +936,7 @@ enum DetectorType { Salesforce = 925; HuggingFace = 926; Snowflake = 927; + Sourcegraph = 928; } message Result { From 78bbb89a307a8f8947c0983fd12f2659570eb13d Mon Sep 17 00:00:00 2001 From: Zubair Khan Date: Thu, 31 Aug 2023 12:06:18 -0400 Subject: [PATCH 15/19] add tri-state verification for twilio detector (#1729) * add tri state for twilio * save progress * fix twilio tristate test * resolve lint issue --- pkg/detectors/twilio/twilio.go | 17 +++++++- pkg/detectors/twilio/twilio_test.go | 68 +++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/pkg/detectors/twilio/twilio.go b/pkg/detectors/twilio/twilio.go index 393033f1..06236493 100644 --- a/pkg/detectors/twilio/twilio.go +++ b/pkg/detectors/twilio/twilio.go @@ -2,6 +2,7 @@ package twilio import ( "context" + "fmt" "net/http" "regexp" @@ -10,12 +11,15 @@ import ( "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 ( + defaultClient = common.SaneHttpClient() 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`) @@ -51,6 +55,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { + client = s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext( ctx, "GET", "https://verify.twilio.com/v2/Services", nil) if err != nil { @@ -65,7 +74,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result if res.StatusCode >= 200 && res.StatusCode < 300 { s1.Verified = true + } else if res.StatusCode == 401 || res.StatusCode == 403 { + // 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 } } diff --git a/pkg/detectors/twilio/twilio_test.go b/pkg/detectors/twilio/twilio_test.go index 8d6b4c30..a6f53eab 100644 --- a/pkg/detectors/twilio/twilio_test.go +++ b/pkg/detectors/twilio/twilio_test.go @@ -6,11 +6,11 @@ package twilio import ( "context" "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "testing" "time" - "github.com/kylelemons/godebug/pretty" - "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -33,11 +33,12 @@ func TestTwilio_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", @@ -52,6 +53,7 @@ func TestTwilio_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_Twilio, Verified: true, Redacted: id, + RawV2: []byte(id + secret), }, }, wantErr: false, @@ -69,6 +71,7 @@ func TestTwilio_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_Twilio, Verified: false, Redacted: id, + RawV2: []byte(id + secretInactive), }, }, wantErr: false, @@ -84,23 +87,62 @@ func TestTwilio_FromChunk(t *testing.T) { want: nil, wantErr: false, }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(100 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a twillio secret %s within awsId %s", secret, id)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Twilio, + Verified: false, + Redacted: id, + RawV2: []byte(id + secret), + }, + }, + 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 twillio secret %s within awsId %s", secret, id)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Twilio, + Verified: false, + Redacted: id, + RawV2: []byte(id + secret), + }, + }, + wantErr: false, + wantVerificationErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { t.Errorf("Twilio.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { if len(got[i].Raw) == 0 { - t.Fatal("no raw secret present") + 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) } - got[i].Raw = nil - got[i].RawV2 = nil } - if diff := pretty.Compare(got, tt.want); diff != "" { + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("Twilio.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) From 2e4b17d3f43d0ae55ec7db91a564aa17712b3beb Mon Sep 17 00:00:00 2001 From: Mike Vanbuskirk Date: Fri, 1 Sep 2023 12:14:36 -0400 Subject: [PATCH 16/19] update jira detector to match new variable tokens (#1720) * update jira detector to match new variable tokens * add versioned interface * use _v2 format for naming packages w. versioner - also added documentation for internal/external contrib. * migrate jira and jira_v2 secrets tests to newer version * add v2 specific domain and email * add support for tri-state verification --------- Co-authored-by: Zubair Khan --- hack/docs/Adding_Detectors_Internal.md | 17 ++- hack/docs/Adding_Detectors_external.md | 12 ++ pkg/detectors/jiratoken/jiratoken.go | 3 + pkg/detectors/jiratoken/jiratoken_test.go | 2 +- pkg/detectors/jiratoken_v2/jiratoken_v2.go | 118 +++++++++++++++++ .../jiratoken_v2/jiratoken_v2_test.go | 123 ++++++++++++++++++ 6 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 pkg/detectors/jiratoken_v2/jiratoken_v2.go create mode 100644 pkg/detectors/jiratoken_v2/jiratoken_v2_test.go diff --git a/hack/docs/Adding_Detectors_Internal.md b/hack/docs/Adding_Detectors_Internal.md index 4b0685be..79db1035 100644 --- a/hack/docs/Adding_Detectors_Internal.md +++ b/hack/docs/Adding_Detectors_Internal.md @@ -44,6 +44,18 @@ If you think that something should be included outside of these guidelines, plea - Go 1.17+ - Make +### Adding New Token Formats to an Existing Scanner + +In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accomodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented. + +1. Create a copy of the package and append `_v2` to the package and file names. Ex: `/` -> `_v2`, `.go` -> `_v2.go` + +Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail. + +2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github_old/github_old.go#L22) + +3. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner) + ### Creating a new Secret Scanner 1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum. @@ -96,7 +108,7 @@ Do not embed test credentials in the test code. Instead, use GCP Secrets Manager Note: `/tmp/s` is a valid path on Linux. You will need to change that for Windows or OSX, otherwise you will see an error. On Windows you will also need to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install). ```bash - gcloud secrets versions access --project trufflehog-testing --secret detectors3 latest > /tmp/s + gcloud secrets versions access --project trufflehog-testing --secret detectors5 latest > /tmp/s ``` 2. Add the secret that you need for testing. @@ -113,8 +125,9 @@ Do not embed test credentials in the test code. Instead, use GCP Secrets Manager 3. Update the secret version with your modification. ```bash - gcloud secrets versions add --project trufflehog-testing detectors3 --data-file /tmp/s + gcloud secrets versions add --project trufflehog-testing detectors5 --data-file /tmp/s ``` + Note: We increment the detectors file name `detectors(n+1)` once the previous one exceeds the max size allowed by GSM (65kb). 4. Access the secret value as shown in the [example code](pkg/detectors/heroku/heroku_test.go). diff --git a/hack/docs/Adding_Detectors_external.md b/hack/docs/Adding_Detectors_external.md index fc9bf42e..1ae072c4 100644 --- a/hack/docs/Adding_Detectors_external.md +++ b/hack/docs/Adding_Detectors_external.md @@ -40,6 +40,18 @@ If you think that something should be included outside of these guidelines, plea - Go 1.17+ - Make +### Adding New Token Formats to an Existing Scanner + +In some instances, services will update their token format, requiring a new regex to properly detect secrets in addition to supporting the previous token format. Accomodating this can be done without adding a net-new detector. [We provide a `Versioner` interface](https://github.com/trufflesecurity/trufflehog/blob/e18cfd5e0af1469a9f05b8d5732bcc94c39da49c/pkg/detectors/detectors.go#L30) that can be implemented. + +1. Create a copy of the package and append `_v2` to the package and file names. Ex: `/` -> `_v2`, `.go` -> `_v2.go` + +Note: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail. + +2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github_old/github_old.go#L22) + +3. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner) + ### Creating a new Secret Scanner 1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum. If necessary, run `make protos` when adding new ones. diff --git a/pkg/detectors/jiratoken/jiratoken.go b/pkg/detectors/jiratoken/jiratoken.go index bfb2fc9e..4b5ed368 100644 --- a/pkg/detectors/jiratoken/jiratoken.go +++ b/pkg/detectors/jiratoken/jiratoken.go @@ -17,6 +17,9 @@ type Scanner struct{} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.Versioner = (*Scanner)(nil) + +func (Scanner) Version() int { return 1 } var ( client = common.SaneHttpClient() diff --git a/pkg/detectors/jiratoken/jiratoken_test.go b/pkg/detectors/jiratoken/jiratoken_test.go index 67e5df3f..460992e5 100644 --- a/pkg/detectors/jiratoken/jiratoken_test.go +++ b/pkg/detectors/jiratoken/jiratoken_test.go @@ -19,7 +19,7 @@ import ( func TestJiraToken_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3") + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } diff --git a/pkg/detectors/jiratoken_v2/jiratoken_v2.go b/pkg/detectors/jiratoken_v2/jiratoken_v2.go new file mode 100644 index 00000000..f6c18e01 --- /dev/null +++ b/pkg/detectors/jiratoken_v2/jiratoken_v2.go @@ -0,0 +1,118 @@ +package jiratoken_v2 + +import ( + "context" + b64 "encoding/base64" + "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{} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.Versioner = (*Scanner)(nil) + +func (Scanner) Version() int { return 2 } + +var ( + client = common.SaneHttpClient() + + // https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ + // Tokens created after Jan 18 2023 use a variable length + tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira"}) + `\b([A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\b`) + domainPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira"}) + `\b([a-zA-Z-0-9]{5,24}\.[a-zA-Z-0-9]{3,16}\.[a-zA-Z-0-9]{3,16})\b`) + emailPat = regexp.MustCompile(detectors.PrefixRegex([]string{"jira"}) + `\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`) +) + +const ( + failedAuth = "AUTHENTICATED_FAILED" + loginReasonHeaderKey = "X-Seraph-LoginReason" +) + +// 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{"jira"} +} + +// FromData will find and optionally verify JiraToken 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) + + tokens := tokenPat.FindAllStringSubmatch(dataStr, -1) + domains := domainPat.FindAllStringSubmatch(dataStr, -1) + emails := emailPat.FindAllStringSubmatch(dataStr, -1) + + for _, email := range emails { + email = strings.Split(email[0], " ") + if len(email) != 2 { + continue + } + resEmail := strings.TrimSpace(email[1]) + for _, token := range tokens { + if len(token) != 2 { + continue + } + resToken := strings.TrimSpace(token[1]) + for _, domain := range domains { + if len(domain) != 2 { + continue + } + resDomain := strings.TrimSpace(domain[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_JiraToken, + Raw: []byte(resToken), + RawV2: []byte(fmt.Sprintf("%s:%s:%s", resEmail, resToken, resDomain)), + } + + if verify { + data := fmt.Sprintf("%s:%s", resEmail, resToken) + sEnc := b64.StdEncoding.EncodeToString([]byte(data)) + req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resDomain+"/rest/api/3/dashboard", nil) + if err != nil { + continue + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + + // If the request is successful and the login reason is not failed authentication, then the token is valid. + // This is because Jira returns a 200 status code even if the token is invalid. + // Jira returns a default dashboard page. + if (res.StatusCode >= 200 && res.StatusCode < 300) && res.Header.Get(loginReasonHeaderKey) != failedAuth { + s1.Verified = true + } else { + s1.VerificationError = err + } + } + } + + if !s1.Verified { + if detectors.IsKnownFalsePositive(string(s1.Raw), detectors.DefaultFalsePositives, true) { + continue + } + } + + results = append(results, s1) + + } + + } + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_JiraToken +} diff --git a/pkg/detectors/jiratoken_v2/jiratoken_v2_test.go b/pkg/detectors/jiratoken_v2/jiratoken_v2_test.go new file mode 100644 index 00000000..cdcc6e87 --- /dev/null +++ b/pkg/detectors/jiratoken_v2/jiratoken_v2_test.go @@ -0,0 +1,123 @@ +//go:build detectors +// +build detectors + +package jiratoken_v2 + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/kylelemons/godebug/pretty" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestJiraToken_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) + } + token := testSecrets.MustGetField("JIRA_V2_TOKEN") + inactiveToken := testSecrets.MustGetField("JIRA_V2_INACTIVE") + email := testSecrets.MustGetField("JIRA_V2_EMAIL") + domain := testSecrets.MustGetField("JIRA_V2_DOMAIN") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a jira secret %s within jira %s with jira %s", token, email, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_JiraToken, + Verified: true, + }, + }, + wantErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a jira secret %s within jira %s but not jira %s valid", inactiveToken, email, domain)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_JiraToken, + Verified: false, + }, + }, + wantErr: 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, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := Scanner{} + got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("JiraToken.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]) + } + got[i].Raw = nil + got[i].RawV2 = nil + } + if diff := pretty.Compare(got, tt.want); diff != "" { + t.Errorf("JiraToken.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) + } + } + }) + } +} From 0a949d7131bad4c6e8fa120620389b2b172693f2 Mon Sep 17 00:00:00 2001 From: "s.shivasurya" Date: Fri, 1 Sep 2023 13:20:18 -0400 Subject: [PATCH 17/19] iterating on suggestions (#1742) --- pkg/detectors/sourcegraph/sourcegraph.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/detectors/sourcegraph/sourcegraph.go b/pkg/detectors/sourcegraph/sourcegraph.go index dda15ec0..bf463fce 100644 --- a/pkg/detectors/sourcegraph/sourcegraph.go +++ b/pkg/detectors/sourcegraph/sourcegraph.go @@ -22,13 +22,13 @@ 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(detectors.PrefixRegex([]string{"sourcegraph", "sg_token"}) + `\b(sgp_[a-z0-9]{40})\b`) + keyPat = regexp.MustCompile(`\b(sgp_[a-f0-9]{40})\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{"sourcegraph", "sg_token"} + return []string{"sgp_"} } // FromData will find and optionally verify Sourcegraph secrets in a given set of bytes. From 000065b22515fc9b20690b467463d6b8b2dc970c Mon Sep 17 00:00:00 2001 From: ahrav Date: Fri, 1 Sep 2023 10:33:21 -0700 Subject: [PATCH 18/19] [chore] - fix sentry detector (#1738) * fix sentry detector to check response. * use err. * address comments. --- pkg/detectors/sentrytoken/sentrytoken.go | 89 +++++++++++++---- pkg/detectors/sentrytoken/sentrytoken_test.go | 98 ++++++++++++++++--- 2 files changed, 156 insertions(+), 31 deletions(-) diff --git a/pkg/detectors/sentrytoken/sentrytoken.go b/pkg/detectors/sentrytoken/sentrytoken.go index 463fbba3..83fc8028 100644 --- a/pkg/detectors/sentrytoken/sentrytoken.go +++ b/pkg/detectors/sentrytoken/sentrytoken.go @@ -2,7 +2,10 @@ package sentrytoken import ( "context" + "encoding/json" + "errors" "fmt" + "io" "net/http" "regexp" "strings" @@ -12,16 +15,20 @@ import ( "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{"sentry"}) + `\b([a-f0-9]{64})\b`) + + errUnauthorized = fmt.Errorf("token unauthorized") ) // Keywords are used for efficiently pre-filtering chunks. @@ -47,32 +54,78 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://sentry.io/api/0/projects/", nil) - if err != nil { - continue + client := s.client + if client == nil { + client = defaultClient } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) - // req.Header.Add("Cookie", "sc=g2ceDPcsBwPLGXugHUv0coGBi09TQYKqmtp0u4JYdIvOTFd28yVvn1yiuXgmJJa8; session=.eJwNyE0OwiAQQGGsPwtP4kqoGQa4gUdgZ2CGiaZJCUqjGxOPbnfve7_h29QpHpVSvbw61To9StvEwzre9TkVbkPcr7jN5dPbNl7X1jUt_a4vLpEHgUycQSgFZ0YBRkGxnthxwIyBAgdngcF4YxBssiMjekGv2245_wHyySMG:1m0J5S:FjpRlBVuFx577UQp6GcSGWTq0uk") - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } else { - // 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 detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } - } + isVerified, verificationErr := verifyToken(ctx, client, resMatch) + + switch { + case errors.Is(verificationErr, errUnauthorized): + s1.Verified = false + case isVerified: + s1.Verified = true + default: + s1.VerificationError = verificationErr } } + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } results = append(results, s1) } return results, nil } +type Response []Project + +type Project struct { + Organization Organization `json:"organization"` +} + +type Organization struct { + ID string `json:"id"` + Name string `json:"name"` +} + +func verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://sentry.io/api/0/projects/", nil) + if err != nil { + return false, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + + res, err := client.Do(req) + if err != nil { + return false, err + } + defer res.Body.Close() + + var isVerified bool + switch res.StatusCode { + case http.StatusOK, http.StatusForbidden: + isVerified = true + case http.StatusUnauthorized: + return false, errUnauthorized + default: + return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + + bytes, readErr := io.ReadAll(res.Body) + if readErr != nil { + return false, readErr + } + + var resp Response + if err = json.Unmarshal(bytes, &resp); err != nil { + return false, err + } + + return isVerified, err +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_SentryToken } diff --git a/pkg/detectors/sentrytoken/sentrytoken_test.go b/pkg/detectors/sentrytoken/sentrytoken_test.go index 332fc127..be0f1041 100644 --- a/pkg/detectors/sentrytoken/sentrytoken_test.go +++ b/pkg/detectors/sentrytoken/sentrytoken_test.go @@ -9,7 +9,9 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "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" @@ -32,11 +34,12 @@ func TestSentryToken_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", @@ -70,6 +73,56 @@ func TestSentryToken_FromChunk(t *testing.T) { }, wantErr: false, }, + { + name: "found, would be verified but for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SentryToken, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found and valid but unexpected api response", + s: Scanner{client: common.ConstantResponseHttpClient(500, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SentryToken, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, good key but wrong scope", + s: Scanner{client: common.ConstantResponseHttpClient(403, responseBody403)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a sentry super secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_SentryToken, + Verified: true, + }, + }, + wantErr: false, + }, { name: "not found", s: Scanner{}, @@ -82,27 +135,46 @@ func TestSentryToken_FromChunk(t *testing.T) { wantErr: false, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("SentryToken.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Gitlab.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]) + t.Fatal("no raw secret present") + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v,", tt.wantVerificationErr, got[i].VerificationError) } - got[i].Raw = nil } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("SentryToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, opts); diff != "" { + t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } } +const responseBody403 = ` +[ + { + "organization": { + "id": "911964", + "slug": "wigslap", + "status": { + "id": "active", + "name": "active" + }, + "name": "wigslap" + } + } +] +` + func BenchmarkFromData(benchmark *testing.B) { ctx := context.Background() s := Scanner{} From c9e60866440d9bd0925a31ce6edbc7ceed9a4000 Mon Sep 17 00:00:00 2001 From: ahrav Date: Fri, 1 Sep 2023 10:38:51 -0700 Subject: [PATCH 19/19] Correctly create azure git links. (#1743) --- pkg/giturl/giturl.go | 4 ++-- pkg/giturl/giturl_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/giturl/giturl.go b/pkg/giturl/giturl.go index 0fdc200e..d3580984 100644 --- a/pkg/giturl/giturl.go +++ b/pkg/giturl/giturl.go @@ -132,9 +132,9 @@ func GenerateLink(repo, commit, file string, line int64) string { return baseLink case providerAzure: - baseLink := repo + "?path=" + file + "&version=GB" + commit + baseLink := repo + "/commit/" + commit + "/" + file if line > 0 { - baseLink += "&line=" + strconv.FormatInt(line, 10) + baseLink += "?line=" + strconv.FormatInt(line, 10) } return baseLink diff --git a/pkg/giturl/giturl_test.go b/pkg/giturl/giturl_test.go index 69c0556c..8035ba51 100644 --- a/pkg/giturl/giturl_test.go +++ b/pkg/giturl/giturl_test.go @@ -148,7 +148,7 @@ func TestGenerateLink(t *testing.T) { commit: "abcdef", file: "main.go", }, - want: "https://dev.azure.com/org/project/_git/repo?path=main.go&version=GBabcdef", + want: "https://dev.azure.com/org/project/_git/repo/commit/abcdef/main.go", }, { name: "Azure link gen with line", @@ -158,7 +158,7 @@ func TestGenerateLink(t *testing.T) { file: "main.go", line: int64(20), }, - want: "https://dev.azure.com/org/project/_git/repo?path=main.go&version=GBabcdef&line=20", + want: "https://dev.azure.com/org/project/_git/repo/commit/abcdef/main.go?line=20", }, } for _, tt := range tests {