diff --git a/pkg/sources/errors.go b/pkg/sources/errors.go index 57f5964f3604..3933a7f0ba68 100644 --- a/pkg/sources/errors.go +++ b/pkg/sources/errors.go @@ -57,3 +57,20 @@ func (s *ScanErrors) Errors() error { defer s.mu.RUnlock() return errors.Join(s.errors...) } + +// TargetedScanError is an error with a secret ID attached. Collections of them can be returned by targeted scans that +// scan multiple targets in order to associate individual errors with individual scan targets. +type TargetedScanError struct { + Err error + SecretID int64 +} + +var _ error = (*TargetedScanError)(nil) + +func (t TargetedScanError) Error() string { + return t.Err.Error() +} + +func (t TargetedScanError) Unwrap() error { + return t.Err +} diff --git a/pkg/sources/github/github.go b/pkg/sources/github/github.go index acffdec1b9bf..043ab145920a 100644 --- a/pkg/sources/github/github.go +++ b/pkg/sources/github/github.go @@ -361,7 +361,8 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, tar // Otherwise, we're scanning all data. // This allows us to only scan the commit where a vulnerability was found. if len(targets) > 0 { - return s.scanTargets(ctx, targets, chunksChan) + errs := s.scanTargets(ctx, targets, chunksChan) + return errors.Join(errs...) } // Reset consumption and rate limit metrics on each run. @@ -1514,14 +1515,16 @@ func (s *Source) chunkPullRequestComments(ctx context.Context, repoInfo repoInfo return nil } -func (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarget, chunksChan chan *sources.Chunk) error { +func (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarget, chunksChan chan *sources.Chunk) []error { + var errs []error for _, tgt := range targets { if err := s.scanTarget(ctx, tgt, chunksChan); err != nil { ctx.Logger().Error(err, "error scanning target") + errs = append(errs, &sources.TargetedScanError{Err: err, SecretID: tgt.SecretID}) } } - return nil + return errs } func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget, chunksChan chan *sources.Chunk) error { diff --git a/pkg/sources/github/github_test.go b/pkg/sources/github/github_test.go index 2e777eb0843a..41e5e3d8cc43 100644 --- a/pkg/sources/github/github_test.go +++ b/pkg/sources/github/github_test.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" + "errors" "fmt" "net/http" "net/url" @@ -18,6 +19,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-github/v62/github" "github.com/stretchr/testify/assert" + "github.com/trufflesecurity/trufflehog/v3/pkg/sources" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/types/known/anypb" "gopkg.in/h2non/gock.v1" @@ -844,3 +846,29 @@ func TestGetGistID(t *testing.T) { assert.Equal(t, tt.expected, got) } } + +// This isn't really a GitHub test, but GitHub is the only source that supports scan targeting right now, so this is +// where I've put this targeted scan test. +func Test_ScanMultipleTargets_MultipleErrors(t *testing.T) { + s := &Source{conn: &sourcespb.GitHub{}} // This test doesn't require initialization + ctx := context.Background() + chunksChan := make(chan *sources.Chunk) + + targets := []sources.ChunkingTarget{ + {SecretID: 1}, + {SecretID: 2}, + } + + // The specific error text doesn't matter for the test, but it has to match what the source generates + want := []*sources.TargetedScanError{ + {SecretID: 1, Err: errors.New("unable to cast metadata type for targeted scan")}, + {SecretID: 2, Err: errors.New("unable to cast metadata type for targeted scan")}, + } + + err := s.Chunks(ctx, chunksChan, targets...) + unwrappable, ok := err.(interface{ Unwrap() []error }) + if assert.True(t, ok, "returned error was not unwrappable") { + got := unwrappable.Unwrap() + assert.ElementsMatch(t, got, want) + } +}