Secret Scanner¶
The Secret Scanner detects secrets (API keys, tokens, and other credentials)
exposed in a team's Postman workspaces. Reachable via client.SecretScanner().
Requires an add-on
The Secret Scanner API requires a Postman Enterprise plan with the Advanced Security Administration add-on enabled โ see Authentication.
Quick Example¶
import (
"context"
postman "github.com/grokify/postman-go"
"github.com/grokify/postman-go/secretscanner"
)
client, _ := postman.NewClient(postman.WithAPIKey(key))
ctx := context.Background()
// List supported secret types.
types, err := client.SecretScanner().SecretTypes(ctx)
// Query detected secrets.
result, err := client.SecretScanner().Query(ctx, &secretscanner.QueryInput{
Limit: 50,
IncludeTotal: true,
})
for _, s := range result.Secrets {
fmt.Printf("%s in %s: %s\n", s.SecretType, s.WorkspaceID, s.Resolution)
}
Methods¶
| Method | Description |
|---|---|
Query(ctx, *QueryInput) |
Query detected secrets, filtered by resolution, secret type, workspace, or resource. Paginated. |
UpdateResolution(ctx, secretID, *UpdateResolutionInput) |
Mark a detected secret as FALSE_POSITIVE, REVOKED, or ACCEPTED_RISK. |
Locations(ctx, secretID, *LocationsInput) |
Find every location (collection, request, environment, ...) where a specific secret was found. |
SecretTypes(ctx) |
List the secret types the scanner supports, including custom team regexes. |
Query¶
result, err := client.SecretScanner().Query(ctx, &secretscanner.QueryInput{
Resolved: false, // only unresolved secrets
Statuses: []secretscanner.Resolution{secretscanner.ResolutionActive},
WorkspaceIDs: []string{"workspace-id"}, // mutually exclusive with Resources
Limit: 50,
IncludeTotal: true,
})
// result.Secrets []DetectedSecret, result.NextCursor, result.Total
A nil input returns all results. Resources and WorkspaceIDs are
mutually exclusive filters.
When Limit is 0, the SDK sends limit=100, the API's maximum page size.
It always sends a limit because, without one, Postman echoes its default page
size back as a string, which the response decoder rejects.
To fetch every page, pass NextCursor back as Cursor until it's empty:
in := &secretscanner.QueryInput{}
for {
page, err := client.SecretScanner().Query(ctx, in)
if err != nil {
return err
}
for _, s := range page.Secrets {
fmt.Println(s.SecretID, s.SecretType)
}
if page.NextCursor == "" {
break
}
in.Cursor = page.NextCursor
}
UpdateResolution¶
_, err := client.SecretScanner().UpdateResolution(ctx, "SECRET_ID",
&secretscanner.UpdateResolutionInput{
Resolution: secretscanner.ResolutionFalsePositive,
WorkspaceID: "WORKSPACE_ID",
})
Resolution accepts ResolutionFalsePositive, ResolutionRevoked, or
ResolutionAcceptedRisk โ ResolutionActive cannot be set directly.
Locations¶
locations, err := client.SecretScanner().Locations(ctx, "SECRET_ID",
&secretscanner.LocationsInput{WorkspaceID: "WORKSPACE_ID"})
for _, loc := range locations.Locations {
fmt.Println(loc.Location, loc.URL, loc.ResourceType)
}
// The secret's resolution history.
for _, e := range locations.ActivityFeed {
fmt.Println(e.Status, e.ResolvedAt, e.ResolvedBy)
}
Limit defaults to 100, as with Query. For a secret that is still active,
the activity-feed entry has no ResolvedAt or ResolvedBy. Those fields are
then empty ("" and 0), and Cursor is empty when there's no cursor.
SecretTypes¶
types, err := client.SecretScanner().SecretTypes(ctx)
for _, t := range types.Types {
fmt.Println(t.ID, t.Name, t.Origin) // Origin: DEFAULT or TEAM_REGEX
}
Exporting to OCSF¶
To send findings to a SIEM or data lake, convert them to OCSF Detection
Finding events with the ocsf package โ see
Exporting Findings to OCSF.
Reference¶
Source: secretscanner/ ยท
GoDoc