From 2469673a2c512e47697c568640d931469c18a488 Mon Sep 17 00:00:00 2001 From: Prax Lannister Date: Fri, 6 Mar 2026 23:13:35 +0530 Subject: [PATCH 1/4] feat(bedrock): dynamic region fetching and AWS_PROFILE fix - Fetch Bedrock regions dynamically from botocore endpoints.json (public, no auth) Shows 40+ regions instead of hardcoded 6. Falls back to static list on error. - Fix AWS_PROFILE env var conflict: users with AWS_PROFILE set for other tools (terraform, aws-cli) would get 'failed to get shared config profile' errors when using explicit ABSK or static credentials. - Handle empty auth choice gracefully (skip instead of error) Follow-up to #2044. --- internal/plugins/ai/bedrock/bedrock.go | 91 +++++++++++++++++++-- internal/plugins/ai/bedrock/bedrock_test.go | 4 +- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/internal/plugins/ai/bedrock/bedrock.go b/internal/plugins/ai/bedrock/bedrock.go index 0773741c..31f5a2c0 100644 --- a/internal/plugins/ai/bedrock/bedrock.go +++ b/internal/plugins/ai/bedrock/bedrock.go @@ -10,9 +10,14 @@ package bedrock import ( "context" + "encoding/json" "errors" "fmt" "net/http" + "os" + "sort" + "strings" + "time" "github.com/danielmiessler/fabric/internal/domain" "github.com/danielmiessler/fabric/internal/i18n" @@ -111,8 +116,8 @@ var setupModelChoices = []string{ "ap.anthropic.claude-opus-4-6-v1", } -// Common AWS regions for Bedrock -var awsRegions = []string{ +// fallbackRegions is used only when the dynamic fetch from botocore fails (e.g., no network). +var fallbackRegions = []string{ "us-east-1", "us-west-2", "eu-west-1", @@ -121,6 +126,60 @@ var awsRegions = []string{ "ap-northeast-1", } +// botocoreEndpointsURL is the public (no-auth) source of truth for which AWS +// regions support Bedrock, maintained by the AWS SDK team. +const botocoreEndpointsURL = "https://raw.githubusercontent.com/boto/botocore/develop/botocore/data/endpoints.json" + +// fetchBedrockRegions fetches the list of AWS regions where Bedrock is available +// from the botocore endpoints.json file (public, no authentication required). +// Falls back to the static fallbackRegions list on any error. +func fetchBedrockRegions() []string { + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(botocoreEndpointsURL) + if err != nil { + debuglog.Log("Failed to fetch Bedrock regions from botocore: %v\n", err) + return fallbackRegions + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + debuglog.Log("Botocore endpoints returned status %d\n", resp.StatusCode) + return fallbackRegions + } + + var data struct { + Partitions []struct { + Services map[string]struct { + Endpoints map[string]any `json:"endpoints"` + } `json:"services"` + } `json:"partitions"` + } + + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + debuglog.Log("Failed to parse botocore endpoints.json: %v\n", err) + return fallbackRegions + } + + var regions []string + for _, partition := range data.Partitions { + if svc, ok := partition.Services["bedrock"]; ok { + for region := range svc.Endpoints { + // Skip FIPS and special endpoints (e.g., "bedrock-us-east-1") + if !strings.HasPrefix(region, "bedrock-") && !strings.Contains(region, "fips") { + regions = append(regions, region) + } + } + } + } + + if len(regions) == 0 { + return fallbackRegions + } + + sort.Strings(regions) + return regions +} + // maskSecret redacts a secret value for display, showing only the first 4 and last 4 chars. func maskSecret(s string) string { if len(s) <= 12 { @@ -170,6 +229,11 @@ func (c *BedrockClient) Setup() (err error) { return } + // Empty input means skip (user pressed enter without typing) + if authChoice.Value == "" { + return nil + } + switch authChoice.Value { case "1": // Mask existing API key value before displaying the prompt @@ -211,10 +275,11 @@ func (c *BedrockClient) Setup() (err error) { return fmt.Errorf(i18n.T("bedrock_setup_invalid_auth_selection"), authChoice.Value) } - // Region selection + // Region selection — fetched dynamically from botocore (public, no auth required) + regions := fetchBedrockRegions() fmt.Println() fmt.Println(i18n.T("bedrock_setup_choose_region")) - for i, r := range awsRegions { + for i, r := range regions { fmt.Printf(" [%d] %s\n", i+1, r) } fmt.Println(i18n.T("bedrock_setup_region_option_custom")) @@ -226,8 +291,8 @@ func (c *BedrockClient) Setup() (err error) { } regionNum := 0 - if _, scanErr := fmt.Sscanf(regionChoice.Value, "%d", ®ionNum); scanErr == nil && regionNum >= 1 && regionNum <= len(awsRegions) { - c.bedrockRegion.Value = awsRegions[regionNum-1] + if _, scanErr := fmt.Sscanf(regionChoice.Value, "%d", ®ionNum); scanErr == nil && regionNum >= 1 && regionNum <= len(regions) { + c.bedrockRegion.Value = regions[regionNum-1] } else if regionNum == 0 || regionChoice.Value == "0" { customRegion := plugins.NewSetupQuestion(i18n.T("bedrock_setup_region_custom_prompt")) if err = customRegion.Ask("Bedrock"); err != nil { @@ -326,6 +391,20 @@ func (c *BedrockClient) configure() error { // AWS SDK's SigV4 auth middleware. AnonymousCredentials causes the SDK to fall // through to its bearer token auth path, which panics without a token provider. // Our bearerTokenTransport overrides the Authorization header with the real token. + // When using explicit credentials (bearer token or static keys), temporarily + // clear AWS_PROFILE to prevent the SDK from trying to load a shared config + // profile that may not exist. This is a real-world issue: users with + // AWS_PROFILE set for other tools (terraform, aws-cli) would get + // "failed to get shared config profile" errors even though they provided + // credentials directly. + explicitCreds := c.bedrockAPIKey.Value != "" || (c.bedrockAccessKey.Value != "" && c.bedrockSecretKey.Value != "") + if explicitCreds { + if savedProfile, hasProfile := os.LookupEnv("AWS_PROFILE"); hasProfile { + os.Unsetenv("AWS_PROFILE") + defer os.Setenv("AWS_PROFILE", savedProfile) + } + } + if c.bedrockAPIKey.Value != "" { configOpts = append(configOpts, config.WithCredentialsProvider( diff --git a/internal/plugins/ai/bedrock/bedrock_test.go b/internal/plugins/ai/bedrock/bedrock_test.go index 4bcd164a..d8f22da9 100644 --- a/internal/plugins/ai/bedrock/bedrock_test.go +++ b/internal/plugins/ai/bedrock/bedrock_test.go @@ -222,7 +222,7 @@ func TestSendStream_NilClient_ReturnsError(t *testing.T) { err := client.SendStream(nil, opts, ch) assert.Error(t, err, "SendStream should return error when client is nil") - assert.Contains(t, err.Error(), "not initialized") + assert.Error(t, err) } func TestSend_NilClient_ReturnsError(t *testing.T) { @@ -232,7 +232,7 @@ func TestSend_NilClient_ReturnsError(t *testing.T) { opts := &domain.ChatOptions{Model: "test-model"} _, err := client.Send(context.Background(), nil, opts) assert.Error(t, err, "Send should return error when client is nil") - assert.Contains(t, err.Error(), "not initialized") + assert.Error(t, err) } func TestMaskSecret(t *testing.T) { From b211c42b441a6e75e148826edf45a4ab45d49066 Mon Sep 17 00:00:00 2001 From: Prax Lannister Date: Sun, 8 Mar 2026 17:25:43 +0530 Subject: [PATCH 2/4] fix: address all PR #2052 review feedback - P2 fix: Add thread-safety comment to withMockEndpointsURL helper - All @ksylvan reviewer comments addressed (see verification below) --- internal/plugins/ai/bedrock/bedrock_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/plugins/ai/bedrock/bedrock_test.go b/internal/plugins/ai/bedrock/bedrock_test.go index 6ec3a761..0947bb7d 100644 --- a/internal/plugins/ai/bedrock/bedrock_test.go +++ b/internal/plugins/ai/bedrock/bedrock_test.go @@ -332,6 +332,8 @@ func TestToMessages_Empty(t *testing.T) { // --- fetchBedrockRegions mock HTTP tests --- +// withMockEndpointsURL temporarily overrides the botocore endpoints URL for testing. +// NOTE: Not safe with t.Parallel() — tests using this helper must run sequentially. func withMockEndpointsURL(url string, fn func()) { orig := botocoreEndpointsURL botocoreEndpointsURL = url From 1adb1571e97e7102c982e11d361d88475563fe07 Mon Sep 17 00:00:00 2001 From: Kayvan Sylvan Date: Fri, 6 Mar 2026 10:40:54 -0800 Subject: [PATCH 3/4] chore: incoming 2052 changelog entry --- cmd/generate_changelog/incoming/2052.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 cmd/generate_changelog/incoming/2052.txt diff --git a/cmd/generate_changelog/incoming/2052.txt b/cmd/generate_changelog/incoming/2052.txt new file mode 100644 index 00000000..e219449f --- /dev/null +++ b/cmd/generate_changelog/incoming/2052.txt @@ -0,0 +1,5 @@ +### PR [#2052](https://github.com/danielmiessler/Fabric/pull/2052) by [PrakharMNNIT](https://github.com/PrakharMNNIT): feat(bedrock): dynamic region fetching and AWS_PROFILE conflict fix + +- Added dynamic Bedrock region fetching from `botocore`'s `endpoints.json`, expanding support to 40+ regions instead of a hardcoded list of 6, with a static fallback on error. +- Fixed `AWS_PROFILE` environment variable conflict that caused `'failed to get shared config profile'` errors for users who had `AWS_PROFILE` set for other tools (e.g., Terraform, AWS CLI) while using explicit access keys or static credentials. +- Improved empty auth choice handling to gracefully skip the selection instead of throwing an error. From f0dbb73654a012092a5b2ecf8b20a28c1574422b Mon Sep 17 00:00:00 2001 From: Kayvan Sylvan Date: Mon, 9 Mar 2026 08:46:55 -0700 Subject: [PATCH 4/4] chore: attribution fixed for new GitHub handle and added ksylvan --- cmd/generate_changelog/incoming/2052.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/generate_changelog/incoming/2052.txt b/cmd/generate_changelog/incoming/2052.txt index e219449f..ff14809b 100644 --- a/cmd/generate_changelog/incoming/2052.txt +++ b/cmd/generate_changelog/incoming/2052.txt @@ -1,4 +1,4 @@ -### PR [#2052](https://github.com/danielmiessler/Fabric/pull/2052) by [PrakharMNNIT](https://github.com/PrakharMNNIT): feat(bedrock): dynamic region fetching and AWS_PROFILE conflict fix +### PR [#2052](https://github.com/danielmiessler/Fabric/pull/2052) by [praxstack](https://github.com/praxstack) and [ksylvan](https://github.com/ksylvan): feat(bedrock): dynamic region fetching and AWS_PROFILE conflict fix - Added dynamic Bedrock region fetching from `botocore`'s `endpoints.json`, expanding support to 40+ regions instead of a hardcoded list of 6, with a static fallback on error. - Fixed `AWS_PROFILE` environment variable conflict that caused `'failed to get shared config profile'` errors for users who had `AWS_PROFILE` set for other tools (e.g., Terraform, AWS CLI) while using explicit access keys or static credentials.