From 1006a35487969027dc591301f58bee4b1125d02a Mon Sep 17 00:00:00 2001 From: Bolaji Olajide <25608335+BolajiOlajide@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:07:43 +0100 Subject: [PATCH 1/2] fix/config: merge env additional headers over config file headers Previously env-derived additional headers overwrote any additionalHeaders loaded from the config file entirely. Merge them instead, with env headers taking precedence, consistent with how the access token and endpoint are overridden. Config-file keys are normalized to lowercase to match the env-header convention, so env values correctly override differently-cased config keys and the authorization-conflict check catches them. --- cmd/src/main.go | 18 +++++++++++++- cmd/src/main_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/cmd/src/main.go b/cmd/src/main.go index fd217ba51f..fbf29cc40b 100644 --- a/cmd/src/main.go +++ b/cmd/src/main.go @@ -337,7 +337,23 @@ func readConfig() (*config, error) { } } - cfg.additionalHeaders = parseAdditionalHeaders() + // Merge additional headers from the environment on top of any headers loaded + // from the config file, rather than overwriting them. Environment-provided + // headers take precedence, consistent with how the access token and endpoint + // are overridden above. parseAdditionalHeaders always returns a non-nil map. + // Config-file keys are used verbatim, but parseAdditionalHeaders always + // lowercases its keys, so normalize the config keys too. Otherwise a + // differently-cased config key (e.g. "Authorization") would neither be + // overridden by its environment counterpart nor be caught by the + // authorization-conflict check below. + envHeaders := parseAdditionalHeaders() + for k, v := range cfg.additionalHeaders { + lk := strings.ToLower(k) + if _, ok := envHeaders[lk]; !ok { + envHeaders[lk] = v + } + } + cfg.additionalHeaders = envHeaders // Ensure that we're not clashing additonal headers _, hasAuthorizationAdditonalHeader := cfg.additionalHeaders["authorization"] if cfg.accessToken != "" && hasAuthorizationAdditonalHeader { diff --git a/cmd/src/main_test.go b/cmd/src/main_test.go index 0b23cb9938..277285b1db 100644 --- a/cmd/src/main_test.go +++ b/cmd/src/main_test.go @@ -358,6 +358,63 @@ func TestReadConfig(t *testing.T) { inCI: true, }, }, + { + name: "config file additional headers preserved when endpoint/token from environment", + fileContents: &configFromFile{ + Endpoint: "https://example.com/", + AccessToken: "deadbeef", + AdditionalHeaders: map[string]string{"x-proxy-token": "secret"}, + }, + envToken: "abc", + envEndpoint: "https://override.com", + want: &config{ + endpointURL: &url.URL{Scheme: "https", Host: "override.com"}, + accessToken: "abc", + additionalHeaders: map[string]string{"x-proxy-token": "secret"}, + }, + }, + { + name: "config file additional headers merged with environment headers", + fileContents: &configFromFile{ + Endpoint: "https://example.com/", + AccessToken: "deadbeef", + AdditionalHeaders: map[string]string{"x-proxy-token": "secret"}, + }, + envFooHeader: "bar", + want: &config{ + endpointURL: &url.URL{Scheme: "https", Host: "example.com"}, + accessToken: "deadbeef", + additionalHeaders: map[string]string{"x-proxy-token": "secret", "foo": "bar"}, + }, + }, + { + name: "environment headers override config file headers", + fileContents: &configFromFile{ + Endpoint: "https://example.com/", + AccessToken: "deadbeef", + AdditionalHeaders: map[string]string{"foo": "from-config"}, + }, + envFooHeader: "from-env", + want: &config{ + endpointURL: &url.URL{Scheme: "https", Host: "example.com"}, + accessToken: "deadbeef", + additionalHeaders: map[string]string{"foo": "from-env"}, + }, + }, + { + name: "environment headers override differently-cased config file headers", + fileContents: &configFromFile{ + Endpoint: "https://example.com/", + AccessToken: "deadbeef", + AdditionalHeaders: map[string]string{"Foo": "from-config"}, + }, + envFooHeader: "from-env", + want: &config{ + endpointURL: &url.URL{Scheme: "https", Host: "example.com"}, + accessToken: "deadbeef", + additionalHeaders: map[string]string{"foo": "from-env"}, + }, + }, } for _, test := range tests { From 41f7daa6fbadd18f9f5a5b1e98a614f4004da5ee Mon Sep 17 00:00:00 2001 From: Bolaji Olajide <25608335+BolajiOlajide@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:11:38 +0100 Subject: [PATCH 2/2] chore/config: condense additional-headers merge comment --- cmd/src/main.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/cmd/src/main.go b/cmd/src/main.go index fbf29cc40b..7bb75e6211 100644 --- a/cmd/src/main.go +++ b/cmd/src/main.go @@ -337,15 +337,8 @@ func readConfig() (*config, error) { } } - // Merge additional headers from the environment on top of any headers loaded - // from the config file, rather than overwriting them. Environment-provided - // headers take precedence, consistent with how the access token and endpoint - // are overridden above. parseAdditionalHeaders always returns a non-nil map. - // Config-file keys are used verbatim, but parseAdditionalHeaders always - // lowercases its keys, so normalize the config keys too. Otherwise a - // differently-cased config key (e.g. "Authorization") would neither be - // overridden by its environment counterpart nor be caught by the - // authorization-conflict check below. + // Merge config-file headers under the env headers (which take precedence), + // lowercasing config keys to match the env-header convention. envHeaders := parseAdditionalHeaders() for k, v := range cfg.additionalHeaders { lk := strings.ToLower(k)