From 72a515ce7456b6d13b4c3336c1a7f699b2467915 Mon Sep 17 00:00:00 2001 From: Nabeel Alam Date: Thu, 20 Feb 2025 02:35:36 +0500 Subject: [PATCH] [Feature] Airtable Analyzer for OAuth Tokens (#3879) * added airtable analyzer for oauth tokens * added airtable analyzer cli command --- pkg/analyzer/analyzers/airtable/airtable.go | 218 ++++++++++++++++++ .../analyzers/airtable/airtable_test.go | 100 ++++++++ .../analyzers/airtable/expected_output.json | 39 ++++ .../analyzers/airtable/permissions.go | 171 ++++++++++++++ .../analyzers/airtable/permissions.yaml | 24 ++ pkg/analyzer/analyzers/airtable/scopes.go | 143 ++++++++++++ pkg/analyzer/analyzers/analyzers.go | 2 + pkg/analyzer/cli.go | 3 + pkg/detectors/airtableoauth/airtableoauth.go | 4 + 9 files changed, 704 insertions(+) create mode 100644 pkg/analyzer/analyzers/airtable/airtable.go create mode 100644 pkg/analyzer/analyzers/airtable/airtable_test.go create mode 100644 pkg/analyzer/analyzers/airtable/expected_output.json create mode 100644 pkg/analyzer/analyzers/airtable/permissions.go create mode 100644 pkg/analyzer/analyzers/airtable/permissions.yaml create mode 100644 pkg/analyzer/analyzers/airtable/scopes.go diff --git a/pkg/analyzer/analyzers/airtable/airtable.go b/pkg/analyzer/analyzers/airtable/airtable.go new file mode 100644 index 000000000000..04ec87f11e30 --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/airtable.go @@ -0,0 +1,218 @@ +//go:generate generate_permissions permissions.yaml permissions.go airtable +package airtable + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + + "github.com/fatih/color" + "github.com/jedib0t/go-pretty/table" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config" + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +var _ analyzers.Analyzer = (*Analyzer)(nil) + +type Analyzer struct { + Cfg *config.Config +} + +func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirtable } + +type AirtableUserInfo struct { + ID string `json:"id"` + Email *string `json:"email,omitempty"` + Scopes []string `json:"scopes"` +} + +type AirtableBases struct { + Bases []struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"bases"` +} + +func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) { + token, ok := credInfo["token"] + if !ok { + return nil, errors.New("token not found in credInfo") + } + + userInfo, err := fetchAirtableUserInfo(token) + if err != nil { + return nil, err + } + + var basesInfo *AirtableBases + if hasScope(userInfo.Scopes, PermissionStrings[SchemaBasesRead]) { + basesInfo, _ = fetchAirtableBases(token) + } + + return mapToAnalyzerResult(userInfo, basesInfo), nil +} + +func AnalyzeAndPrintPermissions(cfg *config.Config, token string) { + userInfo, err := fetchAirtableUserInfo(token) + if err != nil { + color.Red("[x] Error : %s", err.Error()) + return + } + + color.Green("[!] Valid Airtable OAuth2 Access Token\n\n") + printUserAndPermissions(userInfo) + + if hasScope(userInfo.Scopes, PermissionStrings[SchemaBasesRead]) { + var basesInfo *AirtableBases + basesInfo, _ = fetchAirtableBases(token) + printBases(basesInfo) + } +} + +func fetchAirtableUserInfo(token string) (*AirtableUserInfo, error) { + url := "https://api.airtable.com/v0/meta/whoami" + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch Airtable user info, status: %d", resp.StatusCode) + } + + var userInfo AirtableUserInfo + if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { + return nil, err + } + + return &userInfo, nil +} + +func fetchAirtableBases(token string) (*AirtableBases, error) { + url := "https://api.airtable.com/v0/meta/bases" + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch Airtable bases, status: %d", resp.StatusCode) + } + + var basesInfo AirtableBases + if err := json.NewDecoder(resp.Body).Decode(&basesInfo); err != nil { + return nil, err + } + + return &basesInfo, nil +} + +func hasScope(scopes []string, target string) bool { + for _, scope := range scopes { + if scope == target { + return true + } + } + return false +} + +func mapToAnalyzerResult(userInfo *AirtableUserInfo, basesInfo *AirtableBases) *analyzers.AnalyzerResult { + if userInfo == nil { + return nil + } + + result := analyzers.AnalyzerResult{ + AnalyzerType: analyzers.AnalyzerTypeAirtable, + } + var permissions []analyzers.Permission + for _, scope := range userInfo.Scopes { + permissions = append(permissions, analyzers.Permission{Value: scope}) + } + userResource := analyzers.Resource{ + Name: userInfo.ID, + FullyQualifiedName: userInfo.ID, + Type: "user", + Metadata: map[string]any{}, + } + + if userInfo.Email != nil { + userResource.Metadata["email"] = *userInfo.Email + } + + result.Bindings = analyzers.BindAllPermissions(userResource, permissions...) + + if basesInfo != nil { + for _, base := range basesInfo.Bases { + resource := analyzers.Resource{ + Name: base.Name, + FullyQualifiedName: base.ID, + Type: "base", + } + result.UnboundedResources = append(result.UnboundedResources, resource) + } + } + + return &result +} + +func printUserAndPermissions(info *AirtableUserInfo) { + color.Yellow("[i] User:") + t1 := table.NewWriter() + email := "N/A" + if info.Email != nil { + email = *info.Email + } + t1.SetOutputMirror(os.Stdout) + t1.AppendHeader(table.Row{"ID", "Email"}) + t1.AppendRow(table.Row{color.GreenString(info.ID), color.GreenString(email)}) + t1.SetOutputMirror(os.Stdout) + t1.Render() + + color.Yellow("\n[i] Scopes:") + t2 := table.NewWriter() + t2.SetOutputMirror(os.Stdout) + t2.AppendHeader(table.Row{"Scope", "Permission"}) + for _, scope := range info.Scopes { + for i, permission := range scope_mapping[scope] { + scope_string := "" + if i == 0 { + scope_string = scope + } + t2.AppendRow(table.Row{color.GreenString(scope_string), color.GreenString(permission)}) + } + } + t2.Render() + fmt.Printf("%s: https://airtable.com/developers/web/api/scopes\n", color.GreenString("Ref")) +} + +func printBases(bases *AirtableBases) { + color.Yellow("\n[i] Bases:") + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + if len(bases.Bases) > 0 { + t.AppendHeader(table.Row{"ID", "Name"}) + for _, base := range bases.Bases { + t.AppendRow(table.Row{color.GreenString(base.ID), color.GreenString(base.Name)}) + } + } else { + fmt.Printf("%s\n", color.GreenString("No bases associated with token")) + } + t.Render() +} diff --git a/pkg/analyzer/analyzers/airtable/airtable_test.go b/pkg/analyzer/analyzers/airtable/airtable_test.go new file mode 100644 index 000000000000..6b14814dfadc --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/airtable_test.go @@ -0,0 +1,100 @@ +package airtable + +import ( + _ "embed" + "encoding/json" + "sort" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +//go:embed expected_output.json +var expectedOutput []byte + +func TestAnalyzer_Analyze(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + + tests := []struct { + name string + token string + want string // JSON string + wantErr bool + }{ + { + token: testSecrets.MustGetField("AIRTABLEOAUTH_TOKEN"), + name: "valid Airtable OAuth Token", + want: string(expectedOutput), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := Analyzer{Cfg: &config.Config{}} + got, err := a.Analyze(ctx, map[string]string{"token": tt.token}) + if (err != nil) != tt.wantErr { + t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // bindings need to be in the same order to be comparable + sortBindings(got.Bindings) + + // Marshal the actual result to JSON + gotJSON, err := json.Marshal(got) + if err != nil { + t.Fatalf("could not marshal got to JSON: %s", err) + } + + // Parse the expected JSON string + var wantObj analyzers.AnalyzerResult + if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil { + t.Fatalf("could not unmarshal want JSON string: %s", err) + } + + // bindings need to be in the same order to be comparable + sortBindings(wantObj.Bindings) + + // Marshal the expected result to JSON (to normalize) + wantJSON, err := json.Marshal(wantObj) + if err != nil { + t.Fatalf("could not marshal want to JSON: %s", err) + } + + // Compare the JSON strings + if string(gotJSON) != string(wantJSON) { + // Pretty-print both JSON strings for easier comparison + var gotIndented, wantIndented []byte + gotIndented, err = json.MarshalIndent(got, "", " ") + if err != nil { + t.Fatalf("could not marshal got to indented JSON: %s", err) + } + wantIndented, err = json.MarshalIndent(wantObj, "", " ") + if err != nil { + t.Fatalf("could not marshal want to indented JSON: %s", err) + } + t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented) + } + }) + } +} + +// Helper function to sort bindings +func sortBindings(bindings []analyzers.Binding) { + sort.SliceStable(bindings, func(i, j int) bool { + if bindings[i].Resource.Name == bindings[j].Resource.Name { + return bindings[i].Permission.Value < bindings[j].Permission.Value + } + return bindings[i].Resource.Name < bindings[j].Resource.Name + }) +} diff --git a/pkg/analyzer/analyzers/airtable/expected_output.json b/pkg/analyzer/analyzers/airtable/expected_output.json new file mode 100644 index 000000000000..133a704e056a --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/expected_output.json @@ -0,0 +1,39 @@ +{ + "AnalyzerType": 22, + "Bindings": [ + { + "Resource": { + "Name": "usraS0CjAASH3XMpU", + "FullyQualifiedName": "usraS0CjAASH3XMpU", + "Type": "user", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "data.records:read", + "Parent": null + } + }, + { + "Resource": { + "Name": "usraS0CjAASH3XMpU", + "FullyQualifiedName": "usraS0CjAASH3XMpU", + "Type": "user", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "schema.bases:read", + "Parent": null + } + } + ], + "UnboundedResources": [ + { + "Name": "Client Leads and Sales Management", + "FullyQualifiedName": "appzRyj5Q9R9kK6cF", + "Type": "base", + "Parent": null + } + ] +} \ No newline at end of file diff --git a/pkg/analyzer/analyzers/airtable/permissions.go b/pkg/analyzer/analyzers/airtable/permissions.go new file mode 100644 index 000000000000..3329082e0ca8 --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/permissions.go @@ -0,0 +1,171 @@ +// Code generated by go generate; DO NOT EDIT. +package airtable + +import "errors" + +type Permission int + +const ( + Invalid Permission = iota + DataRecordsRead Permission = iota + DataRecordsWrite Permission = iota + DataRecordcommentsRead Permission = iota + DataRecordcommentsWrite Permission = iota + SchemaBasesRead Permission = iota + SchemaBasesWrite Permission = iota + WebhookManage Permission = iota + BlockManage Permission = iota + UserEmailRead Permission = iota + EnterpriseGroupsRead Permission = iota + WorkspacesandbasesRead Permission = iota + WorkspacesandbasesWrite Permission = iota + WorkspacesandbasesSharesManage Permission = iota + EnterpriseScimUsersandgroupsManage Permission = iota + EnterpriseAuditlogsRead Permission = iota + EnterpriseChangeeventsRead Permission = iota + EnterpriseExportsManage Permission = iota + EnterpriseAccountRead Permission = iota + EnterpriseAccountWrite Permission = iota + EnterpriseUserRead Permission = iota + EnterpriseUserWrite Permission = iota + EnterpriseGroupsManage Permission = iota + WorkspacesandbasesManage Permission = iota +) + +var ( + PermissionStrings = map[Permission]string{ + DataRecordsRead: "data.records:read", + DataRecordsWrite: "data.records:write", + DataRecordcommentsRead: "data.recordComments:read", + DataRecordcommentsWrite: "data.recordComments:write", + SchemaBasesRead: "schema.bases:read", + SchemaBasesWrite: "schema.bases:write", + WebhookManage: "webhook:manage", + BlockManage: "block:manage", + UserEmailRead: "user.email:read", + EnterpriseGroupsRead: "enterprise.groups:read", + WorkspacesandbasesRead: "workspacesAndBases:read", + WorkspacesandbasesWrite: "workspacesAndBases:write", + WorkspacesandbasesSharesManage: "workspacesAndBases.shares:manage", + EnterpriseScimUsersandgroupsManage: "enterprise.scim.usersAndGroups:manage", + EnterpriseAuditlogsRead: "enterprise.auditLogs:read", + EnterpriseChangeeventsRead: "enterprise.changeEvents:read", + EnterpriseExportsManage: "enterprise.exports:manage", + EnterpriseAccountRead: "enterprise.account:read", + EnterpriseAccountWrite: "enterprise.account:write", + EnterpriseUserRead: "enterprise.user:read", + EnterpriseUserWrite: "enterprise.user:write", + EnterpriseGroupsManage: "enterprise.groups:manage", + WorkspacesandbasesManage: "workspacesAndBases:manage", + } + + StringToPermission = map[string]Permission{ + "data.records:read": DataRecordsRead, + "data.records:write": DataRecordsWrite, + "data.recordComments:read": DataRecordcommentsRead, + "data.recordComments:write": DataRecordcommentsWrite, + "schema.bases:read": SchemaBasesRead, + "schema.bases:write": SchemaBasesWrite, + "webhook:manage": WebhookManage, + "block:manage": BlockManage, + "user.email:read": UserEmailRead, + "enterprise.groups:read": EnterpriseGroupsRead, + "workspacesAndBases:read": WorkspacesandbasesRead, + "workspacesAndBases:write": WorkspacesandbasesWrite, + "workspacesAndBases.shares:manage": WorkspacesandbasesSharesManage, + "enterprise.scim.usersAndGroups:manage": EnterpriseScimUsersandgroupsManage, + "enterprise.auditLogs:read": EnterpriseAuditlogsRead, + "enterprise.changeEvents:read": EnterpriseChangeeventsRead, + "enterprise.exports:manage": EnterpriseExportsManage, + "enterprise.account:read": EnterpriseAccountRead, + "enterprise.account:write": EnterpriseAccountWrite, + "enterprise.user:read": EnterpriseUserRead, + "enterprise.user:write": EnterpriseUserWrite, + "enterprise.groups:manage": EnterpriseGroupsManage, + "workspacesAndBases:manage": WorkspacesandbasesManage, + } + + PermissionIDs = map[Permission]int{ + DataRecordsRead: 1, + DataRecordsWrite: 2, + DataRecordcommentsRead: 3, + DataRecordcommentsWrite: 4, + SchemaBasesRead: 5, + SchemaBasesWrite: 6, + WebhookManage: 7, + BlockManage: 8, + UserEmailRead: 9, + EnterpriseGroupsRead: 10, + WorkspacesandbasesRead: 11, + WorkspacesandbasesWrite: 12, + WorkspacesandbasesSharesManage: 13, + EnterpriseScimUsersandgroupsManage: 14, + EnterpriseAuditlogsRead: 15, + EnterpriseChangeeventsRead: 16, + EnterpriseExportsManage: 17, + EnterpriseAccountRead: 18, + EnterpriseAccountWrite: 19, + EnterpriseUserRead: 20, + EnterpriseUserWrite: 21, + EnterpriseGroupsManage: 22, + WorkspacesandbasesManage: 23, + } + + IdToPermission = map[int]Permission{ + 1: DataRecordsRead, + 2: DataRecordsWrite, + 3: DataRecordcommentsRead, + 4: DataRecordcommentsWrite, + 5: SchemaBasesRead, + 6: SchemaBasesWrite, + 7: WebhookManage, + 8: BlockManage, + 9: UserEmailRead, + 10: EnterpriseGroupsRead, + 11: WorkspacesandbasesRead, + 12: WorkspacesandbasesWrite, + 13: WorkspacesandbasesSharesManage, + 14: EnterpriseScimUsersandgroupsManage, + 15: EnterpriseAuditlogsRead, + 16: EnterpriseChangeeventsRead, + 17: EnterpriseExportsManage, + 18: EnterpriseAccountRead, + 19: EnterpriseAccountWrite, + 20: EnterpriseUserRead, + 21: EnterpriseUserWrite, + 22: EnterpriseGroupsManage, + 23: WorkspacesandbasesManage, + } +) + +// ToString converts a Permission enum to its string representation +func (p Permission) ToString() (string, error) { + if str, ok := PermissionStrings[p]; ok { + return str, nil + } + return "", errors.New("invalid permission") +} + +// ToID converts a Permission enum to its ID +func (p Permission) ToID() (int, error) { + if id, ok := PermissionIDs[p]; ok { + return id, nil + } + return 0, errors.New("invalid permission") +} + +// PermissionFromString converts a string representation to its Permission enum +func PermissionFromString(s string) (Permission, error) { + if p, ok := StringToPermission[s]; ok { + return p, nil + } + return 0, errors.New("invalid permission string") +} + +// PermissionFromID converts an ID to its Permission enum +func PermissionFromID(id int) (Permission, error) { + if p, ok := IdToPermission[id]; ok { + return p, nil + } + return 0, errors.New("invalid permission ID") +} diff --git a/pkg/analyzer/analyzers/airtable/permissions.yaml b/pkg/analyzer/analyzers/airtable/permissions.yaml new file mode 100644 index 000000000000..a0b999417fde --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/permissions.yaml @@ -0,0 +1,24 @@ +permissions: + - data.records:read + - data.records:write + - data.recordComments:read + - data.recordComments:write + - schema.bases:read + - schema.bases:write + - webhook:manage + - block:manage + - user.email:read + - enterprise.groups:read + - workspacesAndBases:read + - workspacesAndBases:write + - workspacesAndBases.shares:manage + - enterprise.scim.usersAndGroups:manage + - enterprise.auditLogs:read + - enterprise.changeEvents:read + - enterprise.exports:manage + - enterprise.account:read + - enterprise.account:write + - enterprise.user:read + - enterprise.user:write + - enterprise.groups:manage + - workspacesAndBases:manage diff --git a/pkg/analyzer/analyzers/airtable/scopes.go b/pkg/analyzer/analyzers/airtable/scopes.go new file mode 100644 index 000000000000..91de3c9fed97 --- /dev/null +++ b/pkg/analyzer/analyzers/airtable/scopes.go @@ -0,0 +1,143 @@ +package airtable + +var scope_mapping = map[string][]string{ + // Basic Scopes + "data.records:read": { + "List records", + "Get record", + }, + "data.records:write": { + "Create records", + "Update record", + "Update multiple records", + "Delete record", + "Delete multiple records", + "Sync CSV data", + }, + "data.recordComments:read": { + "List comments", + }, + "data.recordComments:write": { + "Create comment", + "Delete comment", + "Update comment", + }, + "schema.bases:read": { + "List bases", + "Get base schema", + }, + "schema.bases:write": { + "Create base", + "Create table", + "Update table", + "Create field", + "Update field", + "Sync CSV data", + }, + "webhook:manage": { + "List webhooks", + "Create a webhook", + "Delete a webhook", + "Enable/disable webhook notifications", + "Refresh a webhook", + }, + "block:manage": { + "Create new releases and submissions for custom extensions", + }, + "user.email:read": { + "See the user's email address", + }, + // Enterprise scopes + "enterprise.groups:read": { + "Get user group", + }, + "workspacesAndBases:read": { + "Get base collaborators", + "List block installations", + "Get interface", + "List views", + "Get view metadata", + "Get workspace collaborators", + }, + "workspacesAndBases:write": { + "Delete block installation", + "Manage block installation", + "Add base collaborator", + "Delete base collaborator", + "Update collaborator base permission", + "Add interface collaborator", + "Delete interface collaborator", + "Update interface collaborator", + "Delete interface invite", + "Delete base invite", + "Delete view", + "Add workspace collaborator", + "Delete workspace collaborator", + "Update workspace collaborator", + "Delete workspace invite", + "Update workspace restrictions", + }, + "workspacesAndBases.shares:manage": { + "List shares", + "Delete share", + "Manage share", + }, + "enterprise.scim.usersAndGroups:manage": { + "List groups", + "Create group", + "Delete group", + "Get group", + "Patch group", + "Put group", + "List users", + "Create user", + "Delete user", + "Get user", + "Patch user", + "Put user", + }, + "enterprise.auditLogs:read": { + "Audit log events", + "List audit log requests", + "Create audit log request", + "Get audit log request", + }, + "enterprise.changeEvents:read": { + "Change events", + }, + "enterprise.exports:manage": { + "List eDiscovery exports", + "Create eDiscovery export", + "Get eDiscovery export", + }, + "enterprise.account:read": { + "Get enterprise", + }, + "enterprise.account:write": { + "Create descendant enterprise", + }, + "enterprise.user:read": { + "Get users by id or email", + "Get user by id", + }, + "enterprise.user:write": { + "Delete users by email", + "Manage user batched", + "Manage user membership", + "Grant admin access", + "Revoke admin access", + "Delete user by id", + "Manage user", + "Logout user", + "Remove user from enterprise", + }, + "enterprise.groups:manage": { + "Move user groups", + }, + "workspacesAndBases:manage": { + "Delete base", + "Move workspaces", + "Delete workspace", + "Move base", + }, +} diff --git a/pkg/analyzer/analyzers/analyzers.go b/pkg/analyzer/analyzers/analyzers.go index 58f7bdfef35b..489486f194de 100644 --- a/pkg/analyzer/analyzers/analyzers.go +++ b/pkg/analyzer/analyzers/analyzers.go @@ -84,6 +84,7 @@ const ( AnalyzerTypeTwilio AnalyzerTypePrivateKey AnalyzerTypeNotion + AnalyzerTypeAirtable // Add new items here with AnalyzerType prefix ) @@ -91,6 +92,7 @@ const ( var analyzerTypeStrings = map[AnalyzerType]string{ AnalyzerTypeInvalid: "Invalid", AnalyzerTypeAirbrake: "Airbrake", + AnalyzerTypeAirtable: "Airtable", AnalyzerAnthropic: "Anthropic", AnalyzerTypeAsana: "Asana", AnalyzerTypeBitbucket: "Bitbucket", diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go index 3221a13f5772..bb211ac634b7 100644 --- a/pkg/analyzer/cli.go +++ b/pkg/analyzer/cli.go @@ -6,6 +6,7 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake" + "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/anthropic" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket" @@ -94,5 +95,7 @@ func Run(keyType string, secretInfo SecretInfo) { dockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["username"], secretInfo.Parts["pat"]) case "anthropic": anthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) + case "airtable": + airtable.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) } } diff --git a/pkg/detectors/airtableoauth/airtableoauth.go b/pkg/detectors/airtableoauth/airtableoauth.go index 90fed8bf70bc..b742439c9987 100644 --- a/pkg/detectors/airtableoauth/airtableoauth.go +++ b/pkg/detectors/airtableoauth/airtableoauth.go @@ -59,6 +59,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1.Verified = isVerified s1.ExtraData = extraData s1.SetVerificationError(verificationErr, match) + + if s1.Verified { + s1.AnalysisInfo = map[string]string{"token": match} + } } results = append(results, s1)