High severityNVD Advisory· Published Oct 23, 2025· Updated Feb 26, 2026
Vault AWS auth method bypass due to AWS client cache
CVE-2025-11621
Description
Vault and Vault Enterprise’s (“Vault”) AWS Auth method may be susceptible to authentication bypass if the role of the configured bound_principal_iam is the same across AWS accounts, or uses a wildcard. This vulnerability, CVE-2025-11621, is fixed in Vault Community Edition 1.21.0 and Vault Enterprise 1.21.0, 1.20.5, 1.19.11, and 1.16.27
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/hashicorp/vaultGo | >= 0.6.0, < 1.21.0 | 1.21.0 |
Affected products
2- HashiCorp/Vault Enterprisev5Range: 0.6.0
Patches
18d07273d14aefix: cache aws auth client by account id (#9981) (#10107)
4 files changed · +67 −41
builtin/credential/aws/backend.go+26 −6 modified@@ -73,17 +73,19 @@ type backend struct { // of tidyCooldownPeriod. nextTidyTime time.Time - // Map to hold the EC2 client objects indexed by region and STS role. + // Map to hold the EC2 client objects indexed by a composite key of Account + // ID, region, and STS role. // This avoids the overhead of creating a client object for every login request. // When the credentials are modified or deleted, all the cached client objects // will be flushed. The empty STS role signifies the master account - EC2ClientsMap map[string]map[string]*ec2.EC2 + EC2ClientsMap map[clientKey]*ec2.EC2 - // Map to hold the IAM client objects indexed by region and STS role. + // Map to hold the IAM client objects indexed by a composite key of Account + // ID, region, and STS role. // This avoids the overhead of creating a client object for every login request. // When the credentials are modified or deleted, all the cached client objects // will be flushed. The empty STS role signifies the master account - IAMClientsMap map[string]map[string]*iam.IAM + IAMClientsMap map[clientKey]*iam.IAM // Map to associate a partition to a random region in that partition. Users of // this don't care what region in the partition they use, but there is some client @@ -117,13 +119,31 @@ type backend struct { deprecatedTerms *strings.Replacer } +// clientKey is a composite key for caching IAM or EC2 clients. +type clientKey struct { + AccountID string + Region string + STSRole string // Use an empty string for the master account +} + +func (c clientKey) String() string { + // For clarity in logs, we explicitly state when the master account is + // being used instead of showing an empty string for the role. + rolePart := c.STSRole + if rolePart == "" { + rolePart = "<master-account>" + } + + return fmt.Sprintf("%s/%s/%s", c.AccountID, c.Region, rolePart) +} + func Backend(_ *logical.BackendConfig) (*backend, error) { b := &backend{ // Setting the periodic func to be run once in an hour. // If there is a real need, this can be made configurable. tidyCooldownPeriod: time.Hour, - EC2ClientsMap: make(map[string]map[string]*ec2.EC2), - IAMClientsMap: make(map[string]map[string]*iam.IAM), + EC2ClientsMap: make(map[clientKey]*ec2.EC2), + IAMClientsMap: make(map[clientKey]*iam.IAM), iamUserIdToArnCache: cache.New(7*24*time.Hour, 24*time.Hour), tidyDenyListCASGuard: new(uint32), tidyAccessListCASGuard: new(uint32),
builtin/credential/aws/client.go+36 −32 modified@@ -175,21 +175,15 @@ func (b *backend) getClientConfig(ctx context.Context, s logical.Storage, region // the cached EC2 client objects will be flushed. Config mutex lock should be // acquired for write operation before calling this method. func (b *backend) flushCachedEC2Clients() { - // deleting items in map during iteration is safe - for region := range b.EC2ClientsMap { - delete(b.EC2ClientsMap, region) - } + b.EC2ClientsMap = make(map[clientKey]*ec2.EC2) } // flushCachedIAMClients deletes all the cached iam client objects from the // backend. If the client credentials configuration is deleted or updated in // the backend, all the cached IAM client objects will be flushed. Config mutex // lock should be acquired for write operation before calling this method. func (b *backend) flushCachedIAMClients() { - // deleting items in map during iteration is safe - for region := range b.IAMClientsMap { - delete(b.IAMClientsMap, region) - } + b.IAMClientsMap = make(map[clientKey]*iam.IAM) } // Gets an entry out of the user ID cache @@ -221,6 +215,11 @@ func (b *backend) stsRoleForAccount(ctx context.Context, s logical.Storage, acco if sts != nil { return sts.StsRole, sts.ExternalID, nil } + + // Return an error if there's no STS config for an account which is not the default one + if b.defaultAWSAccountID != "" && b.defaultAWSAccountID != accountID { + return "", "", fmt.Errorf("no STS configuration found for account ID %q", accountID) + } return "", "", nil } @@ -231,10 +230,16 @@ func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, acco return nil, err } b.configMutex.RLock() - if b.EC2ClientsMap[region] != nil && b.EC2ClientsMap[region][stsRole] != nil { + + key := clientKey{ + AccountID: accountID, + Region: region, + STSRole: stsRole, + } + if cachedClient, ok := b.EC2ClientsMap[key]; ok { defer b.configMutex.RUnlock() // If the client object was already created, return it - return b.EC2ClientsMap[region][stsRole], nil + return cachedClient, nil } // Release the read lock and acquire the write lock @@ -243,8 +248,8 @@ func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, acco defer b.configMutex.Unlock() // If the client gets created while switching the locks, return it - if b.EC2ClientsMap[region] != nil && b.EC2ClientsMap[region][stsRole] != nil { - return b.EC2ClientsMap[region][stsRole], nil + if cachedClient, ok := b.EC2ClientsMap[key]; ok { + return cachedClient, nil } // Create an AWS config object using a chain of providers @@ -267,13 +272,9 @@ func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, acco if client == nil { return nil, fmt.Errorf("could not obtain ec2 client") } - if _, ok := b.EC2ClientsMap[region]; !ok { - b.EC2ClientsMap[region] = map[string]*ec2.EC2{stsRole: client} - } else { - b.EC2ClientsMap[region][stsRole] = client - } - return b.EC2ClientsMap[region][stsRole], nil + b.EC2ClientsMap[key] = client + return b.EC2ClientsMap[key], nil } // clientIAM creates a client to interact with AWS IAM API @@ -283,27 +284,34 @@ func (b *backend) clientIAM(ctx context.Context, s logical.Storage, region, acco return nil, err } if stsRole == "" { - b.Logger().Debug(fmt.Sprintf("no stsRole found for %s", accountID)) + b.Logger().Debug("no stsRole found for account", "accountID", accountID) } else { - b.Logger().Debug(fmt.Sprintf("found stsRole %s for account %s", stsRole, accountID)) + b.Logger().Debug("found stsRole for account", "stsRole", stsRole, "accountID", accountID) } b.configMutex.RLock() - if b.IAMClientsMap[region] != nil && b.IAMClientsMap[region][stsRole] != nil { + + key := clientKey{ + AccountID: accountID, + Region: region, + STSRole: stsRole, + } + if cachedClient, ok := b.IAMClientsMap[key]; ok { defer b.configMutex.RUnlock() // If the client object was already created, return it - b.Logger().Debug(fmt.Sprintf("returning cached client for region %s and stsRole %s", region, stsRole)) - return b.IAMClientsMap[region][stsRole], nil + b.Logger().Debug("returning cached client for key", "key", key) + return cachedClient, nil } - b.Logger().Debug(fmt.Sprintf("no cached client for region %s and stsRole %s", region, stsRole)) + b.Logger().Debug("no cached client for key", "key", key) // Release the read lock and acquire the write lock b.configMutex.RUnlock() b.configMutex.Lock() defer b.configMutex.Unlock() // If the client gets created while switching the locks, return it - if b.IAMClientsMap[region] != nil && b.IAMClientsMap[region][stsRole] != nil { - return b.IAMClientsMap[region][stsRole], nil + if cachedClient, ok := b.IAMClientsMap[key]; ok { + b.Logger().Debug("returning cached client for key", "key", key) + return cachedClient, nil } // Create an AWS config object using a chain of providers @@ -326,12 +334,8 @@ func (b *backend) clientIAM(ctx context.Context, s logical.Storage, region, acco if client == nil { return nil, fmt.Errorf("could not obtain iam client") } - if _, ok := b.IAMClientsMap[region]; !ok { - b.IAMClientsMap[region] = map[string]*iam.IAM{stsRole: client} - } else { - b.IAMClientsMap[region][stsRole] = client - } - return b.IAMClientsMap[region][stsRole], nil + b.IAMClientsMap[key] = client + return b.IAMClientsMap[key], nil } // PluginIdentityTokenFetcher fetches plugin identity tokens from Vault. It is provided
builtin/credential/aws/path_config_rotate_root.go+2 −3 modified@@ -10,7 +10,6 @@ import ( "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/ec2" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam/iamiface" "github.com/hashicorp/go-cleanhttp" @@ -181,8 +180,8 @@ func (b *backend) rotateRoot(ctx context.Context, req *logical.Request) (*logica // Previous cached clients need to be cleared because they may have been made using // the soon-to-be-obsolete credentials. - b.IAMClientsMap = make(map[string]map[string]*iam.IAM) - b.EC2ClientsMap = make(map[string]map[string]*ec2.EC2) + b.flushCachedIAMClients() + b.flushCachedEC2Clients() // Now to clean up the old key. deleteAccessKeyInput := iam.DeleteAccessKeyInput{
changelog/_9981.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +auth/aws: fix an issue where a user may be able to bypass authentication to Vault due to incorrect caching of the AWS client +```
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-9g4h-h484-3578ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-11621ghsaADVISORY
- discuss.hashicorp.com/t/hcsec-2025-30-vault-aws-auth-method-authentication-bypass-through-mishandling-of-cache-entries/76709ghsaWEB
- github.com/hashicorp/vault/commit/8d07273d14ae7f5a48cc96f66cc86615dea83390ghsaWEB
News mentions
0No linked articles in our index yet.