From ad66a23208ed199335354dd767e4e2d7af9187b8 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Sat, 26 Sep 2026 15:59:25 +0000 Subject: [PATCH] fix(failover): treat a set-but-empty api_key_env as unset in ResolveAPIKey Matches cloud-proxy's resolveAPIKey exactly: os.Getenv + empty check rather than os.LookupEnv, so a variable that's set but empty errors instead of probing unauthenticated. Assisted-by: Claude:claude-opus-5-5 --- core/config/model_config.go | 12 +++++++----- core/config/model_config_failover_test.go | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/config/model_config.go b/core/config/model_config.go index 533da24ec..9c38f178d 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -303,19 +303,21 @@ const ( ) // ResolveAPIKey returns the upstream key from api_key_env or api_key_file, or -// "" when neither is set. The cloud-proxy backend applies the same rules. +// "" when neither is set. Mirrored (not imported, to keep backends independent +// of core's package layout) by resolveAPIKey in backend/go/cloud-proxy/proxy.go +// — keep the two in sync, empty-value handling included. func (p ProxyConfig) ResolveAPIKey() (string, error) { switch { case p.APIKeyEnv != "": - v, ok := os.LookupEnv(p.APIKeyEnv) - if !ok { - return "", fmt.Errorf("proxy api_key_env %q is not set", p.APIKeyEnv) + v := os.Getenv(p.APIKeyEnv) + if v == "" { + return "", fmt.Errorf("proxy api_key_env %q is unset", p.APIKeyEnv) } return v, nil case p.APIKeyFile != "": b, err := os.ReadFile(p.APIKeyFile) if err != nil { - return "", fmt.Errorf("proxy api_key_file: %w", err) + return "", fmt.Errorf("proxy api_key_file %q: %w", p.APIKeyFile, err) } return strings.TrimSpace(string(b)), nil } diff --git a/core/config/model_config_failover_test.go b/core/config/model_config_failover_test.go index fe34c5c66..9e82868e9 100644 --- a/core/config/model_config_failover_test.go +++ b/core/config/model_config_failover_test.go @@ -78,6 +78,11 @@ var _ = Describe("ProxyConfig.ResolveAPIKey", func() { _, err := ProxyConfig{APIKeyEnv: "FAILOVER_TEST_UNSET_KEY"}.ResolveAPIKey() Expect(err).To(HaveOccurred()) }) + It("fails on a set-but-empty env var", func() { + GinkgoT().Setenv("FAILOVER_TEST_EMPTY_KEY", "") + _, err := ProxyConfig{APIKeyEnv: "FAILOVER_TEST_EMPTY_KEY"}.ResolveAPIKey() + Expect(err).To(HaveOccurred()) + }) It("reads and trims the key file", func() { f := filepath.Join(GinkgoT().TempDir(), "key") Expect(os.WriteFile(f, []byte(" k2\n"), 0o600)).To(Succeed())