Skip to content

Commit 9ecaf07

Browse files
authored
Update postman metadata (#3852)
* Updated Postman metadata fields to contain location uniqueness and took out the unused fields of global_id, field_name, and variable_type. * Disabled body scanning for now since the only body that is scanned is the currently selected radio button but secrets can still be saved in the other unselected radio button options. * Updated link generation for more accuracy. * Updated tests to not use global constant.
1 parent b6b00bb commit 9ecaf07

File tree

7 files changed

+519
-360
lines changed

7 files changed

+519
-360
lines changed

pkg/output/plain.go

+10
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ func structToMap(obj any) (m map[string]map[string]any, err error) {
110110
return
111111
}
112112
err = json.Unmarshal(data, &m)
113+
// Due to PostmanLocationType protobuf field being an enum, we want to be able to assign the string value of the enum to the field without needing to create another Protobuf field.
114+
// To have the "UNKNOWN_POSTMAN = 0" value be assigned correctly to the field, we need to check if the Postman workspace ID is filled since every secret in the Postman source
115+
// should have a valid workspace ID and the 0 value is considered nil for integers.
116+
if m["Postman"]["workspace_uuid"] != nil {
117+
if m["Postman"]["location_type"] == nil {
118+
m["Postman"]["location_type"] = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN.String()
119+
} else {
120+
m["Postman"]["location_type"] = obj.(*source_metadatapb.MetaData_Postman).Postman.LocationType.String()
121+
}
122+
}
113123
return
114124
}
115125

pkg/pb/source_metadatapb/source_metadata.pb.go

+408-297
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/pb/source_metadatapb/source_metadata.pb.validate.go

+1-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sources/postman/postman.go

+64-44
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
const (
2828
SourceType = sourcespb.SourceType_SOURCE_TYPE_POSTMAN
2929
LINK_BASE_URL = "https://go.postman.co/"
30-
GLOBAL_TYPE = "globals"
3130
ENVIRONMENT_TYPE = "environment"
3231
AUTH_TYPE = "authorization"
3332
REQUEST_TYPE = "request"
@@ -147,7 +146,7 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
147146
if err = json.Unmarshal(contents, &env); err != nil {
148147
return err
149148
}
150-
s.scanVariableData(ctx, chunksChan, Metadata{EnvironmentName: env.ID, fromLocal: true, Link: envPath}, env)
149+
s.scanVariableData(ctx, chunksChan, Metadata{EnvironmentID: env.ID, EnvironmentName: env.Name, fromLocal: true, Link: envPath, LocationType: source_metadatapb.PostmanLocationType_ENVIRONMENT_VARIABLE}, env)
151150
}
152151

153152
// Scan local workspaces
@@ -230,7 +229,9 @@ func (s *Source) scanLocalWorkspace(ctx context.Context, chunksChan chan *source
230229

231230
for _, environment := range workspace.EnvironmentsRaw {
232231
metadata.Link = strings.TrimSuffix(path.Base(filePath), path.Ext(filePath)) + "/environments/" + environment.ID + ".json"
232+
metadata.LocationType = source_metadatapb.PostmanLocationType_ENVIRONMENT_VARIABLE
233233
s.scanVariableData(ctx, chunksChan, metadata, environment)
234+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
234235
}
235236
for _, collection := range workspace.CollectionsRaw {
236237
metadata.Link = strings.TrimSuffix(path.Base(filePath), path.Ext(filePath)) + "/collections/" + collection.Info.PostmanID + ".json"
@@ -265,13 +266,20 @@ func (s *Source) scanWorkspace(ctx context.Context, chunksChan chan *sources.Chu
265266
metadata.Link = LINK_BASE_URL + "environments/" + envID.UUID
266267
metadata.FullID = envVars.ID
267268
metadata.EnvironmentID = envID.UUID
269+
metadata.EnvironmentName = envVars.Name
268270

269271
ctx.Logger().V(2).Info("scanning environment vars", "environment_uuid", metadata.FullID)
270272
for _, word := range strings.Split(envVars.Name, " ") {
271273
s.attemptToAddKeyword(word)
272274
}
273-
275+
metadata.LocationType = source_metadatapb.PostmanLocationType_ENVIRONMENT_VARIABLE
274276
s.scanVariableData(ctx, chunksChan, metadata, envVars)
277+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
278+
metadata.Type = ""
279+
metadata.Link = ""
280+
metadata.FullID = ""
281+
metadata.EnvironmentID = ""
282+
metadata.EnvironmentName = ""
275283
ctx.Logger().V(2).Info("finished scanning environment vars", "environment_uuid", metadata.FullID)
276284
}
277285
ctx.Logger().V(2).Info("finished scanning environments")
@@ -305,11 +313,13 @@ func (s *Source) scanCollection(ctx context.Context, chunksChan chan *sources.Ch
305313
metadata.Link = LINK_BASE_URL + COLLECTION_TYPE + "/" + metadata.FullID
306314
}
307315

316+
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_VARIABLE
308317
// variables must be scanned first before drilling down into the folders and events
309318
// because we need to pick up the substitutions from the top level collection variables
310319
s.scanVariableData(ctx, chunksChan, metadata, VariableData{
311320
KeyValues: collection.Variables,
312321
})
322+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
313323

314324
for _, event := range collection.Events {
315325
s.scanEvent(ctx, chunksChan, metadata, event)
@@ -345,6 +355,7 @@ func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, c
345355

346356
// check if there are any requests in the folder
347357
if item.Request.Method != "" {
358+
metadata.FolderName = strings.Replace(metadata.FolderName, (" > " + item.Name), "", -1)
348359
metadata.RequestID = item.ID
349360
metadata.RequestName = item.Name
350361
metadata.Type = REQUEST_TYPE
@@ -368,8 +379,16 @@ func (s *Source) scanItem(ctx context.Context, chunksChan chan *sources.Chunk, c
368379
s.scanEvent(ctx, chunksChan, metadata, event)
369380
}
370381

382+
if metadata.RequestID != "" {
383+
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_AUTHORIZATION
384+
} else if metadata.FolderID != "" {
385+
metadata.LocationType = source_metadatapb.PostmanLocationType_FOLDER_AUTHORIZATION
386+
} else if metadata.CollectionInfo.UID != "" {
387+
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_AUTHORIZATION
388+
}
371389
// an auth all by its lonesome could be inherited to subfolders and requests
372390
s.scanAuth(ctx, chunksChan, metadata, item.Auth, item.Request.URL)
391+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
373392
}
374393

375394
func (s *Source) scanEvent(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, event Event) {
@@ -378,15 +397,24 @@ func (s *Source) scanEvent(ctx context.Context, chunksChan chan *sources.Chunk,
378397

379398
// Prep direct links. Ignore updating link if it's a local JSON file
380399
if !metadata.fromLocal {
381-
metadata.Link = LINK_BASE_URL + metadata.Type + "/" + metadata.FullID
400+
metadata.Link = LINK_BASE_URL + (strings.Replace(metadata.Type, " > event", "", -1)) + "/" + metadata.FullID
382401
if event.Listen == "prerequest" {
383402
metadata.Link += "?tab=pre-request-scripts"
384403
} else {
385404
metadata.Link += "?tab=tests"
386405
}
387406
}
388407

408+
if strings.Contains(metadata.Type, REQUEST_TYPE) {
409+
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_SCRIPT
410+
} else if strings.Contains(metadata.Type, FOLDER_TYPE) {
411+
metadata.LocationType = source_metadatapb.PostmanLocationType_FOLDER_SCRIPT
412+
} else if strings.Contains(metadata.Type, COLLECTION_TYPE) {
413+
metadata.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_SCRIPT
414+
}
415+
389416
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstitueSet(metadata, data)), metadata)
417+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
390418
}
391419

392420
func (s *Source) scanAuth(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, auth Auth, u URL) {
@@ -471,7 +499,16 @@ func (s *Source) scanAuth(ctx context.Context, chunksChan chan *sources.Chunk, m
471499
s.attemptToAddKeyword(authData)
472500

473501
m.FieldType = AUTH_TYPE
502+
503+
if strings.Contains(m.Type, REQUEST_TYPE) {
504+
m.LocationType = source_metadatapb.PostmanLocationType_REQUEST_AUTHORIZATION
505+
} else if strings.Contains(m.Type, FOLDER_TYPE) {
506+
m.LocationType = source_metadatapb.PostmanLocationType_FOLDER_AUTHORIZATION
507+
} else if strings.Contains(m.Type, COLLECTION_TYPE) {
508+
m.LocationType = source_metadatapb.PostmanLocationType_COLLECTION_AUTHORIZATION
509+
}
474510
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstitueSet(m, authData)), m)
511+
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
475512
}
476513

477514
func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.Chunk, metadata Metadata, r Request) {
@@ -484,66 +521,38 @@ func (s *Source) scanHTTPRequest(ctx context.Context, chunksChan chan *sources.C
484521
KeyValues: r.Header,
485522
}
486523
metadata.Type = originalType + " > header"
524+
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_HEADER
487525
s.scanVariableData(ctx, chunksChan, metadata, vars)
526+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
488527
}
489528

490529
if r.URL.Raw != "" {
491530
metadata.Type = originalType + " > request URL (no query parameters)"
492531
// Note: query parameters are handled separately
493532
u := fmt.Sprintf("%s://%s/%s", r.URL.Protocol, strings.Join(r.URL.Host, "."), strings.Join(r.URL.Path, "/"))
533+
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_URL
494534
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstitueSet(metadata, u)), metadata)
535+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
495536
}
496537

497538
if len(r.URL.Query) > 0 {
498539
vars := VariableData{
499540
KeyValues: r.URL.Query,
500541
}
501542
metadata.Type = originalType + " > GET parameters (query)"
543+
metadata.LocationType = source_metadatapb.PostmanLocationType_REQUEST_QUERY_PARAMETER
502544
s.scanVariableData(ctx, chunksChan, metadata, vars)
545+
metadata.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
503546
}
504547

505548
if r.Auth.Type != "" {
506549
metadata.Type = originalType + " > request auth"
507550
s.scanAuth(ctx, chunksChan, metadata, r.Auth, r.URL)
508551
}
509552

510-
if r.Body.Mode != "" {
511-
metadata.Type = originalType + " > body"
512-
s.scanBody(ctx, chunksChan, metadata, r.Body)
513-
}
514-
}
515-
516-
func (s *Source) scanBody(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, b Body) {
517-
if !m.fromLocal {
518-
m.Link = m.Link + "?tab=body"
519-
}
520-
originalType := m.Type
521-
switch b.Mode {
522-
case "formdata":
523-
m.Type = originalType + " > form data"
524-
vars := VariableData{
525-
KeyValues: b.FormData,
526-
}
527-
s.scanVariableData(ctx, chunksChan, m, vars)
528-
case "urlencoded":
529-
m.Type = originalType + " > url encoded"
530-
vars := VariableData{
531-
KeyValues: b.URLEncoded,
532-
}
533-
s.scanVariableData(ctx, chunksChan, m, vars)
534-
case "raw", "graphql":
535-
data := b.Raw
536-
if b.Mode == "graphql" {
537-
m.Type = originalType + " > graphql"
538-
data = b.GraphQL.Query + " " + b.GraphQL.Variables
539-
}
540-
if b.Mode == "raw" {
541-
m.Type = originalType + " > raw"
542-
}
543-
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstitueSet(m, data)), m)
544-
default:
545-
break
546-
}
553+
// We would scan the body, but currently the body has different radio buttons that can be scanned but only the selected one is scanned. The unselected radio button options can still
554+
// have secrets in them but will not be scanned. The selction of the radio button will also change the secret metadata for that particular scanning pass and can create confusion for
555+
// the user as to the status of a secret. We will reimplement at some point.
547556
}
548557

549558
func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.Chunk, m Metadata, response Response) {
@@ -558,13 +567,17 @@ func (s *Source) scanHTTPResponse(ctx context.Context, chunksChan chan *sources.
558567
KeyValues: response.Header,
559568
}
560569
m.Type = originalType + " > response header"
570+
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_HEADER
561571
s.scanVariableData(ctx, chunksChan, m, vars)
572+
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
562573
}
563574

564575
// Body in a response is just a string
565576
if response.Body != "" {
566577
m.Type = originalType + " > response body"
578+
m.LocationType = source_metadatapb.PostmanLocationType_RESPONSE_BODY
567579
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(s.buildSubstitueSet(m, response.Body)), m)
580+
m.LocationType = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN
568581
}
569582

570583
if response.OriginalRequest.Method != "" {
@@ -600,14 +613,22 @@ func (s *Source) scanVariableData(ctx context.Context, chunksChan chan *sources.
600613
}
601614

602615
m.FieldType = m.Type + " variables"
616+
switch m.FieldType {
617+
case "request > GET parameters (query) variables":
618+
m.Link = m.Link + "?tab=params"
619+
case "request > header variables":
620+
m.Link = m.Link + "?tab=headers"
621+
}
603622
s.scanData(ctx, chunksChan, s.formatAndInjectKeywords(values), m)
604623
}
605624

606625
func (s *Source) scanData(ctx context.Context, chunksChan chan *sources.Chunk, data string, metadata Metadata) {
607626
if data == "" {
608627
return
609628
}
610-
metadata.FieldType = metadata.Type
629+
if metadata.FieldType == "" {
630+
metadata.FieldType = metadata.Type
631+
}
611632

612633
chunksChan <- &sources.Chunk{
613634
SourceType: s.Type(),
@@ -630,8 +651,7 @@ func (s *Source) scanData(ctx context.Context, chunksChan chan *sources.Chunk, d
630651
FolderId: metadata.FolderID,
631652
FolderName: metadata.FolderName,
632653
FieldType: metadata.FieldType,
633-
FieldName: metadata.FieldName,
634-
VariableType: metadata.VarType,
654+
LocationType: metadata.LocationType,
635655
},
636656
},
637657
},

pkg/sources/postman/postman_client.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"io"
77
"net/http"
88
"time"
9+
10+
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
911
)
1012

1113
const (
@@ -68,17 +70,16 @@ type Metadata struct {
6870
EnvironmentID string
6971
CollectionInfo Info
7072
FolderID string // UUID of the folder (but not full ID)
71-
FolderName string
73+
FolderName string // Folder path if the item is nested under one or more folders
7274
RequestID string // UUID of the request (but not full ID)
7375
RequestName string
7476
FullID string //full ID of the reference item (created_by + ID) OR just the UUID
7577
Link string //direct link to the folder (could be .json file path)
7678
Type string //folder, request, etc.
7779
EnvironmentName string
78-
VarType string
79-
FieldName string
80-
FieldType string
80+
FieldType string // Path of the item type
8181
fromLocal bool
82+
LocationType source_metadatapb.PostmanLocationType // The distinct Postman location type that the item falls under
8283
}
8384

8485
type Collection struct {

pkg/sources/postman/substitution_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ func TestSource_BuildSubstituteSet(t *testing.T) {
6666
s := &Source{
6767
sub: NewSubstitution(),
6868
}
69-
s.sub.add(Metadata{Type: GLOBAL_TYPE}, "var1", "value1")
70-
s.sub.add(Metadata{Type: GLOBAL_TYPE}, "var2", "value2")
71-
s.sub.add(Metadata{Type: GLOBAL_TYPE}, "", "value2")
72-
s.sub.add(Metadata{Type: GLOBAL_TYPE}, "continuation_token", "'{{continuation_token}}'") // this caused an infinite loop in the original implementation
73-
s.sub.add(Metadata{Type: GLOBAL_TYPE}, "continuation_token2", "'{{{continuation_token2}}}'") // this caused an infinite loop in the original implementation
69+
s.sub.add(Metadata{Type: ENVIRONMENT_TYPE}, "var1", "value1")
70+
s.sub.add(Metadata{Type: ENVIRONMENT_TYPE}, "var2", "value2")
71+
s.sub.add(Metadata{Type: ENVIRONMENT_TYPE}, "", "value2")
72+
s.sub.add(Metadata{Type: ENVIRONMENT_TYPE}, "continuation_token", "'{{continuation_token}}'") // this caused an infinite loop in the original implementation
73+
s.sub.add(Metadata{Type: ENVIRONMENT_TYPE}, "continuation_token2", "'{{{continuation_token2}}}'") // this caused an infinite loop in the original implementation
7474

7575
metadata := Metadata{
76-
Type: GLOBAL_TYPE,
76+
Type: ENVIRONMENT_TYPE,
7777
}
7878

7979
testCases := []struct {

proto/source_metadata.proto

+25-4
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,11 @@ message AzureRepos {
296296
}
297297

298298
message Postman {
299+
reserved 4, 14, 15;
300+
reserved "globals_id", "field_name", "variable_type";
299301
string link = 1;
300302
string workspace_uuid = 2;
301303
string workspace_name = 3;
302-
string globals_id = 4;
303304
string collection_id = 5;
304305
string collection_name = 6;
305306
string environment_id = 7;
@@ -308,9 +309,29 @@ message Postman {
308309
string request_name = 10;
309310
string folder_id = 11;
310311
string folder_name = 12;
311-
string field_type = 13;
312-
string field_name = 14;
313-
string variable_type = 15;
312+
string field_type = 13; // Do not use this field for transport. It is only used for output to STDOUT.
313+
PostmanLocationType location_type = 16;
314+
}
315+
316+
enum PostmanLocationType {
317+
UNKNOWN_POSTMAN = 0;
318+
REQUEST_QUERY_PARAMETER = 1;
319+
REQUEST_AUTHORIZATION = 2;
320+
REQUEST_HEADER = 3;
321+
REQUEST_BODY_FORM_DATA = 4;
322+
REQUEST_BODY_RAW = 5;
323+
REQUEST_BODY_URL_ENCODED = 6;
324+
REQUEST_BODY_GRAPHQL = 7;
325+
REQUEST_SCRIPT = 8;
326+
REQUEST_URL = 9;
327+
ENVIRONMENT_VARIABLE = 10;
328+
FOLDER_AUTHORIZATION = 11;
329+
FOLDER_SCRIPT = 12;
330+
COLLECTION_SCRIPT = 13;
331+
COLLECTION_VARIABLE = 14;
332+
COLLECTION_AUTHORIZATION = 15;
333+
RESPONSE_BODY = 16;
334+
RESPONSE_HEADER = 17;
314335
}
315336

316337
message Vector {

0 commit comments

Comments
 (0)