From 2432e9eb5bb2ed592e088f2f2cbc411f2d6cc34c Mon Sep 17 00:00:00 2001 From: heishui Date: Tue, 16 Sep 2025 22:41:41 +0800 Subject: [PATCH 1/2] This commit introduces two main changes to the config loading logic to prevent infinite loops. First, it adds loop detection for the source_profile chain. This prevents aws-vault from crashing when a profile has a circular dependency on another profile via source_profile. Second, it removes the implicit inheritance of the default profile for all other profiles. This aligns aws-vault's behavior with the AWS CLI and prevents unexpected loops when the default profile has a source_profile set. This was causing a bug where aws-vault would fail to load a valid AWS config. --- vault/config.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/vault/config.go b/vault/config.go index a4fdc1e7c..880ae9b03 100644 --- a/vault/config.go +++ b/vault/config.go @@ -274,6 +274,7 @@ type ConfigLoader struct { ActiveProfile string visitedProfiles []string + sourceChain map[string]bool } func NewConfigLoader(baseConfig ProfileConfig, file *ConfigFile, activeProfile string) *ConfigLoader { @@ -281,6 +282,7 @@ func NewConfigLoader(baseConfig ProfileConfig, file *ConfigFile, activeProfile s BaseConfig: baseConfig, File: file, ActiveProfile: activeProfile, + sourceChain: make(map[string]bool), } } @@ -405,11 +407,6 @@ func (cl *ConfigLoader) populateFromConfigFile(config *ProfileConfig, profileNam if err != nil { return err } - } else if profileName != defaultSectionName { - err := cl.populateFromConfigFile(config, defaultSectionName) - if err != nil { - return err - } } // Ignore source_profile if it recursively refers to the profile @@ -516,6 +513,14 @@ func (cl *ConfigLoader) hydrateSourceConfig(config *ProfileConfig) error { // GetProfileConfig loads the profile from the config file and environment variables into config func (cl *ConfigLoader) GetProfileConfig(profileName string) (*ProfileConfig, error) { + if cl.sourceChain[profileName] { + return nil, fmt.Errorf("Loop detected in source_profile chain for profile '%s'", profileName) + } + cl.sourceChain[profileName] = true + defer func() { + delete(cl.sourceChain, profileName) + }() + config := cl.BaseConfig config.ProfileName = profileName cl.populateFromEnv(&config) From 8fd3a89223d8ad22a1e1b06182d42fe622fb0678 Mon Sep 17 00:00:00 2001 From: Dave King Date: Wed, 17 Sep 2025 15:10:10 +0800 Subject: [PATCH 2/2] Fix: nil map panic in ConfigLoader tests Replace direct struct instantiation with NewConfigLoader constructor to properly initialize the sourceChain map. This prevents panics when GetProfileConfig tries to assign to the nil map. --- vault/config_test.go | 14 +++++++------- vault/vault_test.go | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/vault/config_test.go b/vault/config_test.go index 95671ca3f..882d83e2f 100644 --- a/vault/config_test.go +++ b/vault/config_test.go @@ -253,7 +253,7 @@ func TestIncludeProfile(t *testing.T) { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "") config, err := configLoader.GetProfileConfig("testincludeprofile2") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -273,7 +273,7 @@ func TestIncludeSsoSession(t *testing.T) { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "") config, err := configLoader.GetProfileConfig("with-sso-session") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -368,7 +368,7 @@ source_profile=foo t.Fatalf("Expected '%s', got '%s'", expectedSourceProfile, def.SourceProfile) } - configLoader := &vault.ConfigLoader{File: configFile} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "") config, err := configLoader.GetProfileConfig("foo") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -405,7 +405,7 @@ source_profile=root t.Fatalf("Expected '%s', got '%s'", expectedSourceProfile, def.SourceProfile) } - configLoader := &vault.ConfigLoader{File: configFile} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "") config, err := configLoader.GetProfileConfig("foo") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -495,7 +495,7 @@ transitive_session_tags = tagOne ,tagTwo,tagThree if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "tagged"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "tagged") config, err := configLoader.GetProfileConfig("tagged") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -532,7 +532,7 @@ transitive_session_tags = tagOne ,tagTwo,tagThree if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "tagged"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "tagged") config, err := configLoader.GetProfileConfig("tagged") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -577,7 +577,7 @@ source_profile = interim if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "target"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "target") config, err := configLoader.GetProfileConfig("target") if err != nil { t.Fatalf("Should have found a profile: %v", err) diff --git a/vault/vault_test.go b/vault/vault_test.go index 75812bab3..4d2855509 100644 --- a/vault/vault_test.go +++ b/vault/vault_test.go @@ -19,7 +19,7 @@ web_identity_token_process = oidccli raw if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "role2"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "role2") config, err := configLoader.GetProfileConfig("role2") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -55,7 +55,7 @@ role_arn=arn:aws:iam::12345678901:role/allow-view-only-access-from-other-account if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "my-shared-base-profile"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "my-shared-base-profile") config, err := configLoader.GetProfileConfig("my-shared-base-profile") if err != nil { t.Fatalf("Should have found a profile: %v", err) @@ -103,7 +103,7 @@ sso_registration_scopes=sso:account:access if err != nil { t.Fatal(err) } - configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "test"} + configLoader := vault.NewConfigLoader(vault.ProfileConfig{}, configFile, "test") config, err := configLoader.GetProfileConfig("test") if err != nil { t.Fatalf("Should have found a profile: %v", err)