Skip to content

Commit ead4289

Browse files
authored
Log recursion limiting (#4236)
* No need to take the address of a map; they're already pointer types * Log whenever we hit the recursion limit
1 parent a9de95b commit ead4289

File tree

3 files changed

+57
-28
lines changed

3 files changed

+57
-28
lines changed

pkg/sources/postman/postman.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,13 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
329329
// scanCollection scans a collection and all its items, folders, and requests.
330330
// locally scoped Metadata is updated as we drill down into the collection.
331331
func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, collection Collection) {
332-
ctx.Logger().V(2).Info("starting to scan collection",
332+
ctx = context.WithValues(ctx,
333333
"collection_name", collection.Info.Name,
334334
"collection_uuid", collection.Info.Uid,
335-
"variable_count", len(collection.Variables))
335+
)
336+
ctx.Logger().V(2).Info("starting to scan collection",
337+
"variable_count", len(collection.Variables),
338+
)
336339
metadata.CollectionInfo = collection.Info
337340
metadata.Type = COLLECTION_TYPE
338341
s.attemptToAddKeyword(collection.Info.Name)
@@ -354,32 +357,34 @@ func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Ch
354357
s.scanAuth(ctx, chunksChan, metadata, collection.Auth, URL{})
355358

356359
ctx.Logger().V(3).Info("Scanning events in collection",
357-
"collection_uid", collection.Info.Uid,
358360
"event_count", len(collection.Events),
359361
)
360362
for _, event := range collection.Events {
361363
s.scanEvent(ctx, chunksChan, metadata, event)
362364
}
363365

364366
ctx.Logger().V(3).Info("Scanning items in collection",
365-
"collection_uid", collection.Info.Uid,
366367
"item_ids", fp.Map(func(i Item) string { return i.Id })(collection.Items),
367368
)
368369
for _, item := range collection.Items {
369-
s.scanItem(ctx, chunksChan, collection, metadata, item, "")
370+
seenItemIds := make(map[string]struct{})
371+
s.scanItem(ctx, chunksChan, collection, metadata, item, "", seenItemIds)
370372
}
371-
372373
}
373374

374-
func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, collection Collection, metadata Metadata, item Item, parentItemId string) {
375+
func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, collection Collection, metadata Metadata, item Item, parentItemId string, seenItemIds map[string]struct{}) {
376+
ctx = context.WithValue(ctx, "item_uid", item.Uid)
377+
375378
ctx.Logger().V(3).Info("Starting to scan item",
376-
"item_uid", item.Uid,
377379
"item_parent_item_id", parentItemId,
378380
"item_descendent_item_uids", fp.Map(func(i Item) string { return i.Uid })(item.Items),
379381
"item_event_count", len(item.Events),
380382
"item_response_count", len(item.Response),
381383
"item_variable_count", len(item.Variable),
382384
)
385+
386+
seenItemIds[item.Uid] = struct{}{}
387+
383388
s.attemptToAddKeyword(item.Name)
384389

385390
// override the base collection metadata with item-specific metadata
@@ -397,7 +402,13 @@ func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, c
397402
}
398403
// recurse through the folders
399404
for _, subItem := range item.Items {
400-
s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Uid)
405+
if _, ok := seenItemIds[subItem.Uid]; ok {
406+
ctx.Logger().Info("Skipping already-seen item",
407+
"seen_item_id", subItem.Uid,
408+
)
409+
continue
410+
}
411+
s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Uid, seenItemIds)
401412
}
402413

403414
// The assignment of the folder ID to be the current item UID is due to wanting to assume that your current item is a folder unless you have request data inside of your item.
@@ -463,7 +474,8 @@ func (s *Source) scanEvent(ctx context.Context, chunksChan chan *sources.Chunk,
463474
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_SCRIPT
464475
}
465476

466-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, data, DefaultMaxRecursionDepth)), metadata)
477+
ctx = context.WithValue(ctx, "event_listen", event.Listen)
478+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, data, DefaultMaxRecursionDepth)), metadata)
467479
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
468480
}
469481

@@ -561,7 +573,7 @@ func (s *Source) scanAuth(ctx context.Context, chunksChan chan *sources.Chunk, m
561573
} else if strings.Contains(m.Type, COLLECTION_TYPE) {
562574
m.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_AUTHORIZATION
563575
}
564-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, authData, DefaultMaxRecursionDepth)), m)
576+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, authData, DefaultMaxRecursionDepth)), m)
565577
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
566578
}
567579

@@ -591,7 +603,7 @@ func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.C
591603
metadata.Type = originalType + " > header"
592604
metadata.Link = metadata.Link + "?tab=headers"
593605
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_HEADER
594-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, strings.Join(r.HeaderString, " "), DefaultMaxRecursionDepth)), metadata)
606+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, strings.Join(r.HeaderString, " "), DefaultMaxRecursionDepth)), metadata)
595607
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
596608
}
597609

@@ -600,7 +612,7 @@ func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.C
600612
// Note: query parameters are handled separately
601613
u := fmt.Sprintf("%s://%s/%s", r.URL.Protocol, strings.Join(r.URL.Host, "."), strings.Join(r.URL.Path, "/"))
602614
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_URL
603-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, u, DefaultMaxRecursionDepth)), metadata)
615+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, u, DefaultMaxRecursionDepth)), metadata)
604616
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
605617
}
606618

@@ -655,13 +667,13 @@ func (s *Source) scanRequestBody(ctx context.Context, chunksChan chan *sources.C
655667
m.Type = originalType + " > raw"
656668
data := b.Raw
657669
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_RAW
658-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, data, DefaultMaxRecursionDepth)), m)
670+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, data, DefaultMaxRecursionDepth)), m)
659671
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
660672
case "graphql":
661673
m.Type = originalType + " > graphql"
662674
data := b.GraphQL.Query + " " + b.GraphQL.Variables
663675
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_GRAPHQL
664-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, data, DefaultMaxRecursionDepth)), m)
676+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, data, DefaultMaxRecursionDepth)), m)
665677
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
666678
}
667679
}
@@ -687,15 +699,15 @@ func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.
687699
m.Type = originalType + " > response header"
688700
// TODO Note: for now, links to Postman responses do not include a more granular tab for the params/header/body, but when they do, we will need to update the metadata.Link info
689701
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_HEADER
690-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, strings.Join(response.HeaderString, " "), DefaultMaxRecursionDepth)), m)
702+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, strings.Join(response.HeaderString, " "), DefaultMaxRecursionDepth)), m)
691703
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
692704
}
693705

694706
// Body in a response is just a string
695707
if response.Body != "" {
696708
m.Type = originalType + " > response body"
697709
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_BODY
698-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, response.Body, DefaultMaxRecursionDepth)), m)
710+
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, response.Body, DefaultMaxRecursionDepth)), m)
699711
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
700712
}
701713

@@ -728,7 +740,7 @@ func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.
728740
if valStr == "" {
729741
continue
730742
}
731-
values = append(values, s.buildSubstituteSet(m, valStr, DefaultMaxRecursionDepth)...)
743+
values = append(values, s.buildSubstituteSet(ctx, m, valStr, DefaultMaxRecursionDepth)...)
732744
}
733745

734746
m.FieldType = m.Type + " variables"

pkg/sources/postman/substitution.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"regexp"
66
"strings"
7+
8+
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
79
)
810

911
var subRe = regexp.MustCompile(`\{\{[^{}]+\}\}`)
@@ -52,12 +54,17 @@ func (s *Source) formatAndInjectKeywords(data []string) string {
5254

5355
// buildSubstituteSet creates a set of substitutions for the given data
5456
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
55-
func (s *Source) buildSubstituteSet(metadata Metadata, data string, maxRecursionDepth int) []string {
57+
func (s *Source) buildSubstituteSet(
58+
ctx context.Context,
59+
metadata Metadata,
60+
data string,
61+
maxRecursionDepth int,
62+
) []string {
5663
var ret []string
5764
combos := make(map[string]struct{})
5865

5966
// Call buildSubstitution with initial depth of 0 and the maxRecursionDepth
60-
s.buildSubstitution(data, metadata, &combos, 0, maxRecursionDepth)
67+
s.buildSubstitution(ctx, data, metadata, combos, 0, maxRecursionDepth)
6168

6269
for combo := range combos {
6370
ret = append(ret, combo)
@@ -73,22 +80,26 @@ func (s *Source) buildSubstituteSet(metadata Metadata, data string, maxRecursion
7380
// depth is the current recursion depth
7481
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
7582
func (s *Source) buildSubstitution(
83+
ctx context.Context,
7684
data string,
7785
metadata Metadata,
78-
combos *map[string]struct{},
86+
combos map[string]struct{},
7987
depth int,
8088
maxRecursionDepth int,
8189
) {
8290
// Limit recursion depth to prevent stack overflow
8391
if depth > maxRecursionDepth {
84-
(*combos)[data] = struct{}{}
92+
ctx.Logger().V(2).Info("Limited recursion depth",
93+
"depth", depth,
94+
)
95+
combos[data] = struct{}{}
8596
return
8697
}
8798

8899
matches := removeDuplicateStr(subRe.FindAllString(data, -1))
89100
if len(matches) == 0 {
90101
// No more substitutions to make, add to combos
91-
(*combos)[data] = struct{}{}
102+
combos[data] = struct{}{}
92103
return
93104
}
94105

@@ -117,14 +128,14 @@ func (s *Source) buildSubstitution(
117128
// Only mark substitution as made if we actually changed something
118129
if d != data {
119130
substitutionMade = true
120-
s.buildSubstitution(d, metadata, combos, depth+1, maxRecursionDepth)
131+
s.buildSubstitution(ctx, d, metadata, combos, depth+1, maxRecursionDepth)
121132
}
122133
}
123134
}
124135

125136
// If no substitutions were made, add the current data
126137
if !substitutionMade {
127-
(*combos)[data] = struct{}{}
138+
combos[data] = struct{}{}
128139
}
129140
}
130141

pkg/sources/postman/substitution_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"sort"
66
"strings"
77
"testing"
8+
9+
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
810
)
911

1012
func TestNewSubstitution(t *testing.T) {
@@ -63,6 +65,8 @@ func TestSource_KeywordCombinations(t *testing.T) {
6365
}
6466

6567
func TestSource_BuildSubstituteSet(t *testing.T) {
68+
ctx := context.Background()
69+
6670
s := &Source{
6771
sub: NewSubstitution(),
6872
}
@@ -89,7 +93,7 @@ func TestSource_BuildSubstituteSet(t *testing.T) {
8993
}
9094

9195
for _, tc := range testCases {
92-
result := s.buildSubstituteSet(metadata, tc.data, DefaultMaxRecursionDepth)
96+
result := s.buildSubstituteSet(ctx, metadata, tc.data, DefaultMaxRecursionDepth)
9397
if !reflect.DeepEqual(result, tc.expected) {
9498
t.Errorf("Expected substitution set: %v, got: %v", tc.expected, result)
9599
}
@@ -158,6 +162,8 @@ func TestSource_FormatAndInjectKeywords(t *testing.T) {
158162
}
159163

160164
func TestSource_BuildSubstitution_RecursionLimit(t *testing.T) {
165+
ctx := context.Background()
166+
161167
s := &Source{
162168
sub: NewSubstitution(),
163169
}
@@ -229,9 +235,9 @@ func TestSource_BuildSubstitution_RecursionLimit(t *testing.T) {
229235

230236
// Use custom maxDepth if provided, otherwise use default
231237
if tc.maxDepth > 0 {
232-
s.buildSubstitution(tc.data, metadata, &combos, 0, tc.maxDepth)
238+
s.buildSubstitution(ctx, tc.data, metadata, combos, 0, tc.maxDepth)
233239
} else {
234-
s.buildSubstitution(tc.data, metadata, &combos, 0, DefaultMaxRecursionDepth)
240+
s.buildSubstitution(ctx, tc.data, metadata, combos, 0, DefaultMaxRecursionDepth)
235241
}
236242

237243
var result []string

0 commit comments

Comments
 (0)