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
This commit is contained in:
@@ -329,10 +329,13 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
|
|||||||
// scanCollection scans a collection and all its items, folders, and requests.
|
// scanCollection scans a collection and all its items, folders, and requests.
|
||||||
// locally scoped Metadata is updated as we drill down into the collection.
|
// locally scoped Metadata is updated as we drill down into the collection.
|
||||||
func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, collection Collection) {
|
func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, collection Collection) {
|
||||||
ctx.Logger().V(2).Info("starting to scan collection",
|
ctx = context.WithValues(ctx,
|
||||||
"collection_name", collection.Info.Name,
|
"collection_name", collection.Info.Name,
|
||||||
"collection_uuid", collection.Info.Uid,
|
"collection_uuid", collection.Info.Uid,
|
||||||
"variable_count", len(collection.Variables))
|
)
|
||||||
|
ctx.Logger().V(2).Info("starting to scan collection",
|
||||||
|
"variable_count", len(collection.Variables),
|
||||||
|
)
|
||||||
metadata.CollectionInfo = collection.Info
|
metadata.CollectionInfo = collection.Info
|
||||||
metadata.Type = COLLECTION_TYPE
|
metadata.Type = COLLECTION_TYPE
|
||||||
s.attemptToAddKeyword(collection.Info.Name)
|
s.attemptToAddKeyword(collection.Info.Name)
|
||||||
@@ -354,7 +357,6 @@ func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Ch
|
|||||||
s.scanAuth(ctx, chunksChan, metadata, collection.Auth, URL{})
|
s.scanAuth(ctx, chunksChan, metadata, collection.Auth, URL{})
|
||||||
|
|
||||||
ctx.Logger().V(3).Info("Scanning events in collection",
|
ctx.Logger().V(3).Info("Scanning events in collection",
|
||||||
"collection_uid", collection.Info.Uid,
|
|
||||||
"event_count", len(collection.Events),
|
"event_count", len(collection.Events),
|
||||||
)
|
)
|
||||||
for _, event := range collection.Events {
|
for _, event := range collection.Events {
|
||||||
@@ -362,24 +364,27 @@ func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger().V(3).Info("Scanning items in collection",
|
ctx.Logger().V(3).Info("Scanning items in collection",
|
||||||
"collection_uid", collection.Info.Uid,
|
|
||||||
"item_ids", fp.Map(func(i Item) string { return i.Id })(collection.Items),
|
"item_ids", fp.Map(func(i Item) string { return i.Id })(collection.Items),
|
||||||
)
|
)
|
||||||
for _, item := range collection.Items {
|
for _, item := range collection.Items {
|
||||||
s.scanItem(ctx, chunksChan, collection, metadata, item, "")
|
seenItemIds := make(map[string]struct{})
|
||||||
|
s.scanItem(ctx, chunksChan, collection, metadata, item, "", seenItemIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, collection Collection, metadata Metadata, item Item, parentItemId string) {
|
func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, collection Collection, metadata Metadata, item Item, parentItemId string, seenItemIds map[string]struct{}) {
|
||||||
|
ctx = context.WithValue(ctx, "item_uid", item.Uid)
|
||||||
|
|
||||||
ctx.Logger().V(3).Info("Starting to scan item",
|
ctx.Logger().V(3).Info("Starting to scan item",
|
||||||
"item_uid", item.Uid,
|
|
||||||
"item_parent_item_id", parentItemId,
|
"item_parent_item_id", parentItemId,
|
||||||
"item_descendent_item_uids", fp.Map(func(i Item) string { return i.Uid })(item.Items),
|
"item_descendent_item_uids", fp.Map(func(i Item) string { return i.Uid })(item.Items),
|
||||||
"item_event_count", len(item.Events),
|
"item_event_count", len(item.Events),
|
||||||
"item_response_count", len(item.Response),
|
"item_response_count", len(item.Response),
|
||||||
"item_variable_count", len(item.Variable),
|
"item_variable_count", len(item.Variable),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
seenItemIds[item.Uid] = struct{}{}
|
||||||
|
|
||||||
s.attemptToAddKeyword(item.Name)
|
s.attemptToAddKeyword(item.Name)
|
||||||
|
|
||||||
// override the base collection metadata with item-specific metadata
|
// 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
|
|||||||
}
|
}
|
||||||
// recurse through the folders
|
// recurse through the folders
|
||||||
for _, subItem := range item.Items {
|
for _, subItem := range item.Items {
|
||||||
s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Uid)
|
if _, ok := seenItemIds[subItem.Uid]; ok {
|
||||||
|
ctx.Logger().Info("Skipping already-seen item",
|
||||||
|
"seen_item_id", subItem.Uid,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.scanItem(ctx, chunksChan, collection, metadata, subItem, item.Uid, seenItemIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// 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,
|
|||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_SCRIPT
|
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_SCRIPT
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, data, DefaultMaxRecursionDepth)), metadata)
|
ctx = context.WithValue(ctx, "event_listen", event.Listen)
|
||||||
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, data, DefaultMaxRecursionDepth)), metadata)
|
||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +573,7 @@ func (s *Source) scanAuth(ctx context.Context, chunksChan chan *sources.Chunk, m
|
|||||||
} else if strings.Contains(m.Type, COLLECTION_TYPE) {
|
} else if strings.Contains(m.Type, COLLECTION_TYPE) {
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_AUTHORIZATION
|
m.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_AUTHORIZATION
|
||||||
}
|
}
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, authData, DefaultMaxRecursionDepth)), m)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, authData, DefaultMaxRecursionDepth)), m)
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +603,7 @@ func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.C
|
|||||||
metadata.Type = originalType + " > header"
|
metadata.Type = originalType + " > header"
|
||||||
metadata.Link = metadata.Link + "?tab=headers"
|
metadata.Link = metadata.Link + "?tab=headers"
|
||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_HEADER
|
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_HEADER
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, strings.Join(r.HeaderString, " "), DefaultMaxRecursionDepth)), metadata)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, strings.Join(r.HeaderString, " "), DefaultMaxRecursionDepth)), metadata)
|
||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,7 +612,7 @@ func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.C
|
|||||||
// Note: query parameters are handled separately
|
// Note: query parameters are handled separately
|
||||||
u := fmt.Sprintf("%s://%s/%s", r.URL.Protocol, strings.Join(r.URL.Host, "."), strings.Join(r.URL.Path, "/"))
|
u := fmt.Sprintf("%s://%s/%s", r.URL.Protocol, strings.Join(r.URL.Host, "."), strings.Join(r.URL.Path, "/"))
|
||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_URL
|
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_URL
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(metadata, u, DefaultMaxRecursionDepth)), metadata)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, metadata, u, DefaultMaxRecursionDepth)), metadata)
|
||||||
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,13 +667,13 @@ func (s *Source) scanRequestBody(ctx context.Context, chunksChan chan *sources.C
|
|||||||
m.Type = originalType + " > raw"
|
m.Type = originalType + " > raw"
|
||||||
data := b.Raw
|
data := b.Raw
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_RAW
|
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_RAW
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, data, DefaultMaxRecursionDepth)), m)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, data, DefaultMaxRecursionDepth)), m)
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
case "graphql":
|
case "graphql":
|
||||||
m.Type = originalType + " > graphql"
|
m.Type = originalType + " > graphql"
|
||||||
data := b.GraphQL.Query + " " + b.GraphQL.Variables
|
data := b.GraphQL.Query + " " + b.GraphQL.Variables
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_GRAPHQL
|
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_BODY_GRAPHQL
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, data, DefaultMaxRecursionDepth)), m)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, data, DefaultMaxRecursionDepth)), m)
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -687,7 +699,7 @@ func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.
|
|||||||
m.Type = originalType + " > response header"
|
m.Type = originalType + " > response header"
|
||||||
// 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
|
// 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
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_HEADER
|
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_HEADER
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, strings.Join(response.HeaderString, " "), DefaultMaxRecursionDepth)), m)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, strings.Join(response.HeaderString, " "), DefaultMaxRecursionDepth)), m)
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +707,7 @@ func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.
|
|||||||
if response.Body != "" {
|
if response.Body != "" {
|
||||||
m.Type = originalType + " > response body"
|
m.Type = originalType + " > response body"
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_BODY
|
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_BODY
|
||||||
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(m, response.Body, DefaultMaxRecursionDepth)), m)
|
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstituteSet(ctx, m, response.Body, DefaultMaxRecursionDepth)), m)
|
||||||
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +740,7 @@ func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.
|
|||||||
if valStr == "" {
|
if valStr == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
values = append(values, s.buildSubstituteSet(m, valStr, DefaultMaxRecursionDepth)...)
|
values = append(values, s.buildSubstituteSet(ctx, m, valStr, DefaultMaxRecursionDepth)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.FieldType = m.Type + " variables"
|
m.FieldType = m.Type + " variables"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subRe = regexp.MustCompile(`\{\{[^{}]+\}\}`)
|
var subRe = regexp.MustCompile(`\{\{[^{}]+\}\}`)
|
||||||
@@ -52,12 +54,17 @@ func (s *Source) formatAndInjectKeywords(data []string) string {
|
|||||||
|
|
||||||
// buildSubstituteSet creates a set of substitutions for the given data
|
// buildSubstituteSet creates a set of substitutions for the given data
|
||||||
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
|
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
|
||||||
func (s *Source) buildSubstituteSet(metadata Metadata, data string, maxRecursionDepth int) []string {
|
func (s *Source) buildSubstituteSet(
|
||||||
|
ctx context.Context,
|
||||||
|
metadata Metadata,
|
||||||
|
data string,
|
||||||
|
maxRecursionDepth int,
|
||||||
|
) []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
combos := make(map[string]struct{})
|
combos := make(map[string]struct{})
|
||||||
|
|
||||||
// Call buildSubstitution with initial depth of 0 and the maxRecursionDepth
|
// Call buildSubstitution with initial depth of 0 and the maxRecursionDepth
|
||||||
s.buildSubstitution(data, metadata, &combos, 0, maxRecursionDepth)
|
s.buildSubstitution(ctx, data, metadata, combos, 0, maxRecursionDepth)
|
||||||
|
|
||||||
for combo := range combos {
|
for combo := range combos {
|
||||||
ret = append(ret, combo)
|
ret = append(ret, combo)
|
||||||
@@ -73,22 +80,26 @@ func (s *Source) buildSubstituteSet(metadata Metadata, data string, maxRecursion
|
|||||||
// depth is the current recursion depth
|
// depth is the current recursion depth
|
||||||
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
|
// maxRecursionDepth is the maximum recursion depth to use for variable substitution
|
||||||
func (s *Source) buildSubstitution(
|
func (s *Source) buildSubstitution(
|
||||||
|
ctx context.Context,
|
||||||
data string,
|
data string,
|
||||||
metadata Metadata,
|
metadata Metadata,
|
||||||
combos *map[string]struct{},
|
combos map[string]struct{},
|
||||||
depth int,
|
depth int,
|
||||||
maxRecursionDepth int,
|
maxRecursionDepth int,
|
||||||
) {
|
) {
|
||||||
// Limit recursion depth to prevent stack overflow
|
// Limit recursion depth to prevent stack overflow
|
||||||
if depth > maxRecursionDepth {
|
if depth > maxRecursionDepth {
|
||||||
(*combos)[data] = struct{}{}
|
ctx.Logger().V(2).Info("Limited recursion depth",
|
||||||
|
"depth", depth,
|
||||||
|
)
|
||||||
|
combos[data] = struct{}{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := removeDuplicateStr(subRe.FindAllString(data, -1))
|
matches := removeDuplicateStr(subRe.FindAllString(data, -1))
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
// No more substitutions to make, add to combos
|
// No more substitutions to make, add to combos
|
||||||
(*combos)[data] = struct{}{}
|
combos[data] = struct{}{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,14 +128,14 @@ func (s *Source) buildSubstitution(
|
|||||||
// Only mark substitution as made if we actually changed something
|
// Only mark substitution as made if we actually changed something
|
||||||
if d != data {
|
if d != data {
|
||||||
substitutionMade = true
|
substitutionMade = true
|
||||||
s.buildSubstitution(d, metadata, combos, depth+1, maxRecursionDepth)
|
s.buildSubstitution(ctx, d, metadata, combos, depth+1, maxRecursionDepth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no substitutions were made, add the current data
|
// If no substitutions were made, add the current data
|
||||||
if !substitutionMade {
|
if !substitutionMade {
|
||||||
(*combos)[data] = struct{}{}
|
combos[data] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewSubstitution(t *testing.T) {
|
func TestNewSubstitution(t *testing.T) {
|
||||||
@@ -63,6 +65,8 @@ func TestSource_KeywordCombinations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSource_BuildSubstituteSet(t *testing.T) {
|
func TestSource_BuildSubstituteSet(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
s := &Source{
|
s := &Source{
|
||||||
sub: NewSubstitution(),
|
sub: NewSubstitution(),
|
||||||
}
|
}
|
||||||
@@ -89,7 +93,7 @@ func TestSource_BuildSubstituteSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
result := s.buildSubstituteSet(metadata, tc.data, DefaultMaxRecursionDepth)
|
result := s.buildSubstituteSet(ctx, metadata, tc.data, DefaultMaxRecursionDepth)
|
||||||
if !reflect.DeepEqual(result, tc.expected) {
|
if !reflect.DeepEqual(result, tc.expected) {
|
||||||
t.Errorf("Expected substitution set: %v, got: %v", tc.expected, result)
|
t.Errorf("Expected substitution set: %v, got: %v", tc.expected, result)
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,8 @@ func TestSource_FormatAndInjectKeywords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSource_BuildSubstitution_RecursionLimit(t *testing.T) {
|
func TestSource_BuildSubstitution_RecursionLimit(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
s := &Source{
|
s := &Source{
|
||||||
sub: NewSubstitution(),
|
sub: NewSubstitution(),
|
||||||
}
|
}
|
||||||
@@ -229,9 +235,9 @@ func TestSource_BuildSubstitution_RecursionLimit(t *testing.T) {
|
|||||||
|
|
||||||
// Use custom maxDepth if provided, otherwise use default
|
// Use custom maxDepth if provided, otherwise use default
|
||||||
if tc.maxDepth > 0 {
|
if tc.maxDepth > 0 {
|
||||||
s.buildSubstitution(tc.data, metadata, &combos, 0, tc.maxDepth)
|
s.buildSubstitution(ctx, tc.data, metadata, combos, 0, tc.maxDepth)
|
||||||
} else {
|
} else {
|
||||||
s.buildSubstitution(tc.data, metadata, &combos, 0, DefaultMaxRecursionDepth)
|
s.buildSubstitution(ctx, tc.data, metadata, combos, 0, DefaultMaxRecursionDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
|
|||||||
Reference in New Issue
Block a user