Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package trufflehog for openSUSE:Factory checked in at 2025-03-14 23:51:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/trufflehog (Old) and /work/SRC/openSUSE:Factory/.trufflehog.new.19136 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "trufflehog" Fri Mar 14 23:51:25 2025 rev:80 rq:1252889 version:3.88.17 Changes: -------- --- /work/SRC/openSUSE:Factory/trufflehog/trufflehog.changes 2025-03-11 20:46:01.695974033 +0100 +++ /work/SRC/openSUSE:Factory/.trufflehog.new.19136/trufflehog.changes 2025-03-14 23:51:50.047985926 +0100 @@ -1,0 +2,8 @@ +Thu Mar 13 21:12:47 UTC 2025 - felix.niederwan...@suse.com + +- Update to version 3.88.17: + * fix(deps): update module golang.org/x/net to v0.36.0 [security] (#3975) + * updated anthropic analyzer to analyze admin keys (#3973) + * [Fix] Upgraded AWS go-sdk to V2 (only for AWS Canary Token Verification) (#3907) + +------------------------------------------------------------------- Old: ---- trufflehog-3.88.16.obscpio New: ---- trufflehog-3.88.17.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ trufflehog.spec ++++++ --- /var/tmp/diff_new_pack.SoBQsQ/_old 2025-03-14 23:51:50.880020583 +0100 +++ /var/tmp/diff_new_pack.SoBQsQ/_new 2025-03-14 23:51:50.884020749 +0100 @@ -17,7 +17,7 @@ Name: trufflehog -Version: 3.88.16 +Version: 3.88.17 Release: 0 Summary: CLI tool to find exposed secrets in source and archives License: AGPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.SoBQsQ/_old 2025-03-14 23:51:50.928022582 +0100 +++ /var/tmp/diff_new_pack.SoBQsQ/_new 2025-03-14 23:51:50.932022749 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/trufflesecurity/trufflehog.git</param> <param name="scm">git</param> <param name="revision">main</param> - <param name="version">v3.88.16</param> + <param name="version">v3.88.17</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> </service> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.SoBQsQ/_old 2025-03-14 23:51:50.952023582 +0100 +++ /var/tmp/diff_new_pack.SoBQsQ/_new 2025-03-14 23:51:50.956023748 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/trufflesecurity/trufflehog.git</param> - <param name="changesrevision">544cf2318a3471dc8ca39a526466fd8d9558da12</param></service></servicedata> + <param name="changesrevision">12164e38f0f1b673ab0594c7d94daf71b0be6823</param></service></servicedata> (No newline at EOF) ++++++ trufflehog-3.88.16.obscpio -> trufflehog-3.88.17.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/go.mod new/trufflehog-3.88.17/go.mod --- old/trufflehog-3.88.16/go.mod 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/go.mod 2025-03-13 18:42:32.000000000 +0100 @@ -25,6 +25,11 @@ github.com/alecthomas/kingpin/v2 v2.4.0 github.com/avast/apkparser v0.0.0-20240729092610-90591e0804ae github.com/aws/aws-sdk-go v1.55.6 + github.com/aws/aws-sdk-go-v2 v1.36.1 + github.com/aws/aws-sdk-go-v2/config v1.29.6 + github.com/aws/aws-sdk-go-v2/credentials v1.17.59 + github.com/aws/aws-sdk-go-v2/service/sns v1.33.19 + github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 github.com/aymanbagabas/go-osc52 v1.2.1 github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb @@ -103,11 +108,12 @@ go.uber.org/automaxprocs v1.6.0 go.uber.org/mock v0.5.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.32.0 - golang.org/x/net v0.34.0 + golang.org/x/crypto v0.35.0 + golang.org/x/net v0.36.0 golang.org/x/oauth2 v0.25.0 golang.org/x/sync v0.11.0 golang.org/x/text v0.22.0 + golang.org/x/time v0.8.0 google.golang.org/api v0.214.0 google.golang.org/protobuf v1.36.5 gopkg.in/h2non/gock.v1 v1.1.2 @@ -148,7 +154,15 @@ github.com/andybalholm/brotli v1.1.1 // indirect github.com/apache/arrow/go/v14 v14.0.2 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/smithy-go v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -311,7 +325,6 @@ golang.org/x/mod v0.22.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect - golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/go.sum new/trufflehog-3.88.17/go.sum --- old/trufflehog-3.88.16/go.sum 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/go.sum 2025-03-13 18:42:32.000000000 +0100 @@ -118,8 +118,34 @@ github.com/avast/apkparser v0.0.0-20240729092610-90591e0804ae/go.mod h1:GNvprXNmXaDjpHmN3RFxz5QdK5VXTUvmQludCbjoBy4= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= +github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= +github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg= +github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ= +github.com/aws/aws-sdk-go-v2/credentials v1.17.59 h1:9btwmrt//Q6JcSdgJOLI98sdr5p7tssS9yAsGe8aKP4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdWidyZuI1ghNe2xjzUZAyT+08= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.19 h1:ghgWtf6FnkD6YqDUq65Zg5lzQ92xADHBoJdWUyChiFw= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.19/go.mod h1:/TQAkYgLlLoH1/2Y9qgaE460iPWhdq67emlW/ue42U8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -851,6 +877,8 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -918,6 +946,8 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/analyzer/analyzers/anthropic/anthropic.go new/trufflehog-3.88.17/pkg/analyzer/analyzers/anthropic/anthropic.go --- old/trufflehog-3.88.16/pkg/analyzer/analyzers/anthropic/anthropic.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/analyzer/analyzers/anthropic/anthropic.go 2025-03-13 18:42:32.000000000 +0100 @@ -4,6 +4,7 @@ import ( "errors" "os" + "strings" "github.com/fatih/color" "github.com/jedib0t/go-pretty/v6/table" @@ -17,7 +18,8 @@ const ( // Key Types - APIKey = "API-Key" + APIKey = "API-Key" + AdminKey = "Admin-Key" ) type Analyzer struct { @@ -28,7 +30,6 @@ type SecretInfo struct { Valid bool Type string // key type - TODO: Handle Anthropic Admin Keys - Reference string AnthropicResources []AnthropicResource Permissions string // always full_access Misc map[string]string @@ -39,6 +40,7 @@ ID string Name string Type string + Parent *AnthropicResource Metadata map[string]string } @@ -73,7 +75,7 @@ } if info.Valid { - color.Green("[!] Valid Anthropic API key\n\n") + color.Green("[!] Valid Anthropic %s\n\n", info.Type) // no user information // print full access permission printPermission(info.Permissions) @@ -88,16 +90,23 @@ // create a HTTP client client := analyzers.NewAnalyzeClient(cfg) - var secretInfo = &SecretInfo{ - Type: APIKey, // TODO: implement Admin-Key type as well - } + keyType := getKeyType(key) - if err := listModels(client, key, secretInfo); err != nil { - return nil, err + var secretInfo = &SecretInfo{ + Type: keyType, } - if err := listMessageBatches(client, key, secretInfo); err != nil { - return nil, err + switch keyType { + case APIKey: + if err := captureAPIKeyResources(client, key, secretInfo); err != nil { + return nil, err + } + case AdminKey: + if err := captureAdminKeyResources(client, key, secretInfo); err != nil { + return nil, err + } + default: + return nil, errors.New("unsupported key type") } // anthropic key has full access only @@ -133,6 +142,14 @@ }, } + if Anthropicresource.Parent != nil { + binding.Resource.Parent = &analyzers.Resource{ + Name: Anthropicresource.Parent.Name, + FullyQualifiedName: Anthropicresource.Parent.ID, + Type: Anthropicresource.Parent.Type, + } + } + for key, value := range Anthropicresource.Metadata { binding.Resource.Metadata[key] = value } @@ -162,3 +179,14 @@ } t.Render() } + +// getKeyType return the type of key +func getKeyType(key string) string { + if strings.Contains(key, "sk-ant-admin01") { + return AdminKey + } else if strings.Contains(key, "sk-ant-api03") { + return APIKey + } + + return "" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/analyzer/analyzers/anthropic/requests.go new/trufflehog-3.88.17/pkg/analyzer/analyzers/anthropic/requests.go --- old/trufflehog-3.88.16/pkg/analyzer/analyzers/anthropic/requests.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/analyzer/analyzers/anthropic/requests.go 2025-03-13 18:42:32.000000000 +0100 @@ -7,6 +7,18 @@ "net/http" ) +var endpoints = map[string]string{ + // api key endpoints + "models": "https://api.anthropic.com/v1/models", + "messageBatches": "https://api.anthropic.com/v1/messages/batches", + + // admin key endpoints + "orgUsers": "https://api.anthropic.com/v1/organizations/users", + "workspaces": "https://api.anthropic.com/v1/organizations/workspaces", + "workspaceMembers": "https://api.anthropic.com/v1/organizations/workspaces/%s/members", // require workspace id + "apiKeys": "https://api.anthropic.com/v1/organizations/api_keys", +} + type ModelsResponse struct { Data []struct { ID string `json:"id"` @@ -25,6 +37,47 @@ } `json:"data"` } +type OrgUsersResponse struct { + Data []struct { + ID string `json:"id"` + Type string `json:"type"` + Email string `json:"email"` + Name string `json:"name"` + Role string `json:"role"` + } `json:"data"` +} + +type WorkspacesResponse struct { + Data []struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + } `json:"data"` +} + +type WorkspaceMembersResponse struct { + Data []struct { + WorkspaceID string `json:"workspace_id"` + UserID string `json:"user_id"` + Type string `json:"type"` + WorkspaceRole string `json:"workspace_role"` + } `json:"data"` +} + +type APIKeysResponse struct { + Data []struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + WorkspaceID string `json:"workspace_id"` + CreatedBy struct { + ID string `json:"id"` + } `json:"created_by"` + PartialKeyHint string `json:"partial_key_hint"` + Status string `json:"status"` + } `json:"data"` +} + // makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) { // create request @@ -56,8 +109,38 @@ return responseBodyByte, resp.StatusCode, nil } -func listModels(client *http.Client, key string, secretInfo *SecretInfo) error { - response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/models", key) +// captureAPIKeyResources capture resources associated with api key +func captureAPIKeyResources(client *http.Client, apiKey string, secretInfo *SecretInfo) error { + if err := captureModels(client, apiKey, secretInfo); err != nil { + return err + } + + if err := captureMessageBatches(client, apiKey, secretInfo); err != nil { + return err + } + + return nil +} + +// captureAdminKeyResources capture resources associated with admin key +func captureAdminKeyResources(client *http.Client, adminKey string, secretInfo *SecretInfo) error { + if err := captureOrgUsers(client, adminKey, secretInfo); err != nil { + return err + } + + if err := captureWorkspaces(client, adminKey, secretInfo); err != nil { + return err + } + + if err := captureAPIKeys(client, adminKey, secretInfo); err != nil { + return err + } + + return nil +} + +func captureModels(client *http.Client, apiKey string, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, endpoints["models"], apiKey) if err != nil { return err } @@ -86,8 +169,8 @@ } } -func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo) error { - response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/messages/batches", key) +func captureMessageBatches(client *http.Client, apiKey string, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, endpoints["messageBatches"], apiKey) if err != nil { return err } @@ -111,6 +194,143 @@ }, }) } + + return nil + case http.StatusNotFound, http.StatusUnauthorized: + return fmt.Errorf("invalid/revoked api-key") + default: + return fmt.Errorf("unexpected status code: %d while fetching models", statusCode) + } +} + +func captureOrgUsers(client *http.Client, adminKey string, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, endpoints["orgUsers"], adminKey) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var users OrgUsersResponse + + if err := json.Unmarshal(response, &users); err != nil { + return err + } + + for _, user := range users.Data { + secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{ + ID: user.ID, + Name: user.Name, + Type: user.Type, + Metadata: map[string]string{ + "Role": user.Role, + "Email": user.Email, + }, + }) + } + + return nil + case http.StatusNotFound, http.StatusUnauthorized: + return fmt.Errorf("invalid/revoked api-key") + default: + return fmt.Errorf("unexpected status code: %d while fetching models", statusCode) + } +} + +func captureWorkspaces(client *http.Client, adminKey string, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, endpoints["workspaces"], adminKey) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var workspaces WorkspacesResponse + + if err := json.Unmarshal(response, &workspaces); err != nil { + return err + } + + for _, workspace := range workspaces.Data { + resource := AnthropicResource{ + ID: workspace.ID, + Name: workspace.Name, + Type: workspace.Type, + } + + secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, resource) + // capture each workspace members + if err := captureWorkspaceMembers(client, adminKey, resource, secretInfo); err != nil { + return err + } + } + + return nil + case http.StatusNotFound, http.StatusUnauthorized: + return fmt.Errorf("invalid/revoked api-key") + default: + return fmt.Errorf("unexpected status code: %d while fetching models", statusCode) + } +} + +func captureWorkspaceMembers(client *http.Client, key string, parentWorkspace AnthropicResource, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, fmt.Sprintf(endpoints["workspaceMembers"], parentWorkspace.ID), key) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var members WorkspaceMembersResponse + + if err := json.Unmarshal(response, &members); err != nil { + return err + } + + for _, member := range members.Data { + secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{ + ID: fmt.Sprintf("anthropic/workspace/%s/member/%s", member.WorkspaceID, member.UserID), + Name: member.UserID, + Type: member.Type, + Parent: &parentWorkspace, + }) + } + + return nil + case http.StatusNotFound, http.StatusUnauthorized: + return fmt.Errorf("invalid/revoked api-key") + default: + return fmt.Errorf("unexpected status code: %d while fetching models", statusCode) + } +} + +func captureAPIKeys(client *http.Client, adminKey string, secretInfo *SecretInfo) error { + response, statusCode, err := makeAnthropicRequest(client, endpoints["apiKeys"], adminKey) + if err != nil { + return err + } + + switch statusCode { + case http.StatusOK: + var apiKeys APIKeysResponse + + if err := json.Unmarshal(response, &apiKeys); err != nil { + return err + } + + for _, apiKey := range apiKeys.Data { + secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{ + ID: apiKey.ID, + Name: apiKey.Name, + Type: apiKey.Type, + Metadata: map[string]string{ + "WorkspaceID": apiKey.WorkspaceID, + "CreatedBy": apiKey.CreatedBy.ID, + "PartialKeyHint": apiKey.PartialKeyHint, + "Status": apiKey.Status, + }, + }) + } return nil case http.StatusNotFound, http.StatusUnauthorized: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/common/http.go new/trufflehog-3.88.17/pkg/common/http.go --- old/trufflehog-3.88.16/pkg/common/http.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/common/http.go 2025-03-13 18:42:32.000000000 +0100 @@ -89,7 +89,7 @@ T http.RoundTripper } -func userAgent() string { +func UserAgent() string { if len(feature.UserAgentSuffix.Load()) > 0 { return "TruffleHog " + feature.UserAgentSuffix.Load() } @@ -97,7 +97,7 @@ } func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Add("User-Agent", userAgent()) + req.Header.Add("User-Agent", UserAgent()) return t.T.RoundTrip(req) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/accesskey.go new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/accesskey.go --- old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/accesskey.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/accesskey.go 2025-03-13 18:42:32.000000000 +0100 @@ -2,14 +2,17 @@ import ( "context" - "encoding/hex" - "encoding/json" "fmt" - "io" + "net" "net/http" "strings" "time" + "github.com/aws/aws-sdk-go-v2/aws/middleware" + awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/sts" regexp "github.com/wasilibs/go-re2" "github.com/trufflesecurity/trufflehog/v3/pkg/common" @@ -20,7 +23,7 @@ ) type scanner struct { - verificationClient *http.Client + verificationClient config.HTTPClient skipIDs map[string]struct{} detectors.DefaultMultiPartCredentialProvider } @@ -55,7 +58,6 @@ } = (*scanner)(nil) var ( - defaultVerificationClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. // Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids @@ -72,6 +74,32 @@ } } +// The recommended way by AWS is to use the SDK's http client. +// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-http.html +// Note: Using default http.Client causes SignatureInvalid error in response. therefore, based on http default client implementation, we are using the same configuration. +func getDefaultBuildableClient() *awshttp.BuildableClient { + return awshttp.NewBuildableClient(). + WithTimeout(common.DefaultResponseTimeout). + WithDialerOptions(func(dialer *net.Dialer) { + dialer.Timeout = 2 * time.Second + dialer.KeepAlive = 5 * time.Second + }). + WithTransportOptions(func(tr *http.Transport) { + tr.Proxy = http.ProxyFromEnvironment + tr.MaxIdleConns = 5 + tr.IdleConnTimeout = 5 * time.Second + tr.TLSHandshakeTimeout = 3 * time.Second + tr.ExpectContinueTimeout = 1 * time.Second + }) +} + +func (s scanner) getAWSBuilableClient() config.HTTPClient { + if s.verificationClient == nil { + s.verificationClient = getDefaultBuildableClient() + } + return s.verificationClient +} + // FromData will find and optionally verify AWS secrets in a given set of bytes. func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { logger := logContext.AddLogger(ctx).Logger().WithName("aws") @@ -127,7 +155,7 @@ isCanary = true s1.ExtraData["message"] = thinkstMessage if verify { - verified, arn, err := s.verifyCanary(idMatch, secretMatch) + verified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch) s1.Verified = verified if arn != "" { s1.ExtraData["arn"] = arn @@ -139,7 +167,7 @@ isCanary = true s1.ExtraData["message"] = thinkstKnockoffsMessage if verify { - verified, arn, err := s.verifyCanary(idMatch, secretMatch) + verified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch) s1.Verified = verified if arn != "" { s1.ExtraData["arn"] = arn @@ -154,7 +182,7 @@ } if verify && !isCanary { - isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, true) + isVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, len(secretMatches) > 1) s1.Verified = isVerified // Log if the calculated ID does not match the ID value from verification. @@ -199,117 +227,53 @@ ) func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, retryOn403 bool) (bool, map[string]string, error) { - // REQUEST VALUES. - now := time.Now().UTC() - datestamp := now.Format("20060102") - amzDate := now.Format("20060102T150405Z0700") - - req, err := http.NewRequestWithContext(ctx, method, endpoint, nil) + // Prep AWS Creds for STS + cfg, err := config.LoadDefaultConfig(ctx, + config.WithRegion(region), + config.WithHTTPClient(s.getAWSBuilableClient()), + config.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, ""), + ), + ) if err != nil { return false, nil, err } - req.Header.Set("Accept", "application/json") - - // TASK 1: CREATE A CANONICAL REQUEST. - // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - canonicalURI := "/" - canonicalHeaders := "host:" + host + "\n" - signedHeaders := "host" - algorithm := "AWS4-HMAC-SHA256" - credentialScope := fmt.Sprintf("%s/%s/%s/aws4_request", datestamp, region, service) - - params := req.URL.Query() - params.Add("Action", "GetCallerIdentity") - params.Add("Version", "2011-06-15") - params.Add("X-Amz-Algorithm", algorithm) - params.Add("X-Amz-Credential", resIDMatch+"/"+credentialScope) - params.Add("X-Amz-Date", amzDate) - params.Add("X-Amz-Expires", "30") - params.Add("X-Amz-SignedHeaders", signedHeaders) - - canonicalQuerystring := params.Encode() - payloadHash := aws.GetHash("") // empty payload - canonicalRequest := method + "\n" + canonicalURI + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash - - // TASK 2: CREATE THE STRING TO SIGN. - stringToSign := algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + aws.GetHash(canonicalRequest) - - // TASK 3: CALCULATE THE SIGNATURE. - // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - hash := aws.GetHMAC([]byte(fmt.Sprintf("AWS4%s", resSecretMatch)), []byte(datestamp)) - hash = aws.GetHMAC(hash, []byte(region)) - hash = aws.GetHMAC(hash, []byte(service)) - hash = aws.GetHMAC(hash, []byte("aws4_request")) - - signature2 := aws.GetHMAC(hash, []byte(stringToSign)) // Get Signature HMAC SHA256 - signature := hex.EncodeToString(signature2) - - // TASK 4: ADD SIGNING INFORMATION TO THE REQUEST. - params.Add("X-Amz-Signature", signature) - req.Header.Add("Content-type", "application/x-www-form-urlencoded; charset=utf-8") - req.URL.RawQuery = params.Encode() - - client := s.verificationClient - if client == nil { - client = defaultVerificationClient - } + // Create STS client + stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) { + o.APIOptions = append(o.APIOptions, middleware.AddUserAgentKeyValue("User-Agent", common.UserAgent())) + }) - res, err := client.Do(req) + // Make the GetCallerIdentity API call + resp, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) if err != nil { - return false, nil, err - } - defer func() { - _, _ = io.Copy(io.Discard, res.Body) - _ = res.Body.Close() - }() - - // TODO: tighten range of acceptable status codes - if res.StatusCode >= 200 && res.StatusCode < 300 { - identityInfo := aws.IdentityResponse{} - if err := json.NewDecoder(res.Body).Decode(&identityInfo); err != nil { - return false, nil, err - } - - extraData := map[string]string{ - "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", - "account": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account, - "user_id": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID, - "arn": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn, - } - return true, extraData, nil - } else if res.StatusCode == 403 { - // Experimentation has indicated that if you make two GetCallerIdentity requests within five seconds that + // Experimentation has indicated that if you make multiple GetCallerIdentity requests within five seconds that // share a key ID but are signed with different secrets the second one will be rejected with a 403 that // carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is // valid. Since this is exactly our access pattern, we need to work around it. // // Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The - // response to the resubmission will be as expected. But there's a caveat: You can't have closed the body of - // the response to the original second request, or read to its end, or the resubmission will also yield a - // SignatureDoesNotMatch. For this reason, we have to re-request all 403s. We can't re-request only - // SignatureDoesNotMatch responses, because we can only tell whether a given 403 is a SignatureDoesNotMatch - // after decoding its response body, which requires reading the entire response body, which disables the - // workaround. + // response to the resubmission will be as expected. // // We are clearly deep in the guts of AWS implementation details here, so this all might change with no // notice. If you're here because something in this detector broke, you have my condolences. - if retryOn403 { - return s.verifyMatch(ctx, resIDMatch, resSecretMatch, false) - } - - var body aws.ErrorResponseBody - if err = json.NewDecoder(res.Body).Decode(&body); err != nil { - return false, nil, fmt.Errorf("couldn't parse the sts response body (%v)", err) - } - // All instances of the code I've seen in the wild are PascalCased but this check is - // case-insensitive out of an abundance of caution - if strings.EqualFold(body.Error.Code, "InvalidClientTokenId") { + if strings.Contains(err.Error(), "StatusCode: 403") { + if retryOn403 { + return s.verifyMatch(ctx, resIDMatch, resSecretMatch, false) + } + return false, nil, nil + } else if strings.Contains(err.Error(), "InvalidClientTokenId") { return false, nil, nil } - return false, nil, fmt.Errorf("request returned status %d with an unexpected reason (%s: %s)", res.StatusCode, body.Error.Code, body.Error.Message) - } else { - return false, nil, fmt.Errorf("request to %v returned unexpected status %d", res.Request.URL, res.StatusCode) + return false, nil, fmt.Errorf("request returned unexpected error: %w", err) + } + + extraData := map[string]string{ + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + "account": *resp.Account, + "user_id": *resp.UserId, + "arn": *resp.Arn, } + return true, extraData, nil } func (s scanner) CleanResults(results []detectors.Result) []detectors.Result { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/accesskey_integration_test.go new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/accesskey_integration_test.go --- old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/accesskey_integration_test.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/accesskey_integration_test.go 2025-03-13 18:42:32.000000000 +0100 @@ -5,11 +5,12 @@ import ( "context" - "crypto/sha256" "fmt" + "sort" "testing" "time" + "github.com/brianvoe/gofakeit/v7" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" @@ -95,9 +96,8 @@ id := testSecrets.MustGetField("AWS_ID") inactiveSecret := testSecrets.MustGetField("AWS_INACTIVE") inactiveID := id[:len(id)-3] + "XYZ" - hasher := sha256.New() - hasher.Write([]byte(inactiveSecret)) - hash := string(hasher.Sum(nil)) + + hash := gofakeit.Password(true, true, true, false, false, 10) type args struct { ctx context.Context @@ -233,15 +233,6 @@ "user_id": "AIDAZAVB57H5V3Q4ACRGM", }, }, - { - DetectorType: detectorspb.DetectorType_AWS, - Verified: false, - Redacted: inactiveID, - ExtraData: map[string]string{ - "account": "619888638459", - "resource_type": "Access key", - }, - }, }, wantErr: false, }, @@ -389,6 +380,15 @@ want: []detectors.Result{ { DetectorType: detectorspb.DetectorType_AWS, + Verified: false, + Redacted: "AKIAZAVB57H55F3T4BKH", + ExtraData: map[string]string{ + "resource_type": "Access key", + "account": "619888638459", + }, + }, + { + DetectorType: detectorspb.DetectorType_AWS, Verified: true, Redacted: "AKIAZAVB57H55F3T4BKH", ExtraData: map[string]string{ @@ -425,6 +425,8 @@ return x.Redacted < y.Redacted }), } + + sortResults(tt.want) if diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != "" { t.Errorf("AWS.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } @@ -432,6 +434,13 @@ } } +// Helper function to sort results due to the order of the redacted +func sortResults(results []detectors.Result) { + sort.SliceStable(results, func(i, j int) bool { + return results[i].Redacted < results[j].Redacted + }) +} + func BenchmarkFromData(benchmark *testing.B) { ctx := context.Background() s := scanner{} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/canary.go new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/canary.go --- old/trufflehog-3.88.16/pkg/detectors/aws/access_keys/canary.go 2025-03-10 17:46:29.000000000 +0100 +++ new/trufflehog-3.88.17/pkg/detectors/aws/access_keys/canary.go 2025-03-13 18:42:32.000000000 +0100 @@ -1,12 +1,15 @@ package access_keys import ( + "context" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sns" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/middleware" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" ) const thinkstMessage = "This is an AWS canary token generated at canarytokens.org, and was not set off; learn more here: https://trufflesecurity.com/canaries" @@ -44,21 +47,24 @@ } ) -func (s scanner) verifyCanary(resIDMatch, resSecretMatch string) (bool, string, error) { +func (s scanner) verifyCanary(ctx context.Context, resIDMatch, resSecretMatch string) (bool, string, error) { // Prep AWS Creds for SNS - sess := session.Must(session.NewSession(&aws.Config{ - Region: aws.String("us-east-1"), // any region seems to work - Credentials: credentials.NewStaticCredentials( - resIDMatch, - resSecretMatch, - "", + cfg, err := config.LoadDefaultConfig(ctx, + config.WithRegion(region), + config.WithHTTPClient(s.getAWSBuilableClient()), + config.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, ""), ), - HTTPClient: s.verificationClient, - })) - svc := sns.New(sess) + ) + if err != nil { + return false, "", err + } + svc := sns.NewFromConfig(cfg, func(o *sns.Options) { + o.APIOptions = append(o.APIOptions, middleware.AddUserAgentKeyValue("User-Agent", common.UserAgent())) + }) // Prep vars and Publish to SNS - _, err := svc.Publish(&sns.PublishInput{ + _, err = svc.Publish(ctx, &sns.PublishInput{ Message: aws.String("foo"), PhoneNumber: aws.String("1"), }) @@ -69,7 +75,7 @@ return true, arn, nil } else if strings.Contains(err.Error(), "does not match the signature you provided") { return false, "", nil - } else if strings.Contains(err.Error(), "status code: 403") { + } else if strings.Contains(err.Error(), "status code: 403") || strings.Contains(err.Error(), "InvalidClientTokenId") { return false, "", nil } else { return false, "", err ++++++ trufflehog.obsinfo ++++++ --- /var/tmp/diff_new_pack.SoBQsQ/_old 2025-03-14 23:51:52.088070903 +0100 +++ /var/tmp/diff_new_pack.SoBQsQ/_new 2025-03-14 23:51:52.096071236 +0100 @@ -1,5 +1,5 @@ name: trufflehog -version: 3.88.16 -mtime: 1741625189 -commit: 544cf2318a3471dc8ca39a526466fd8d9558da12 +version: 3.88.17 +mtime: 1741887752 +commit: 12164e38f0f1b673ab0594c7d94daf71b0be6823 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/trufflehog/vendor.tar.gz /work/SRC/openSUSE:Factory/.trufflehog.new.19136/vendor.tar.gz differ: char 5, line 1