From 6aa03f2ec5d3efa9ca68a85b3bee92bebea2e521 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Fri, 26 Jun 2026 14:15:11 -0600 Subject: [PATCH] Pass JOB_TOKEN and DEPENDABOT_API_URL to the proxy container Extract proxy environment construction into a proxyEnv helper. Forward JOB_TOKEN (from the host) and DEPENDABOT_API_URL (from params.ApiUrl) together, and only when JOB_TOKEN is set and non-empty. The proxy uses DEPENDABOT_API_URL to choose which host to inject the JOB_TOKEN into as an Authorization header. Forwarding it without a token would cause the proxy to clobber auth on requests to that host (e.g. the Azure DevOps host in LOCAL_AZURE_ACCESS_TOKEN flows), so the two are gated on JOB_TOKEN being present. Also honor a host-provided PROXY_CACHE value, falling back to true when it is unset or empty, and forward OPENSSL_FORCE_FIPS_MODE through to the proxy when the host has it set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/infra/proxy.go | 39 ++++++--- internal/infra/proxy_test.go | 163 +++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 internal/infra/proxy_test.go diff --git a/internal/infra/proxy.go b/internal/infra/proxy.go index 773c07e6..f3646334 100644 --- a/internal/infra/proxy.go +++ b/internal/infra/proxy.go @@ -79,16 +79,7 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets * } config := &container.Config{ Image: params.ProxyImage, - Env: []string{ - "HTTP_PROXY=" + os.Getenv("HTTP_PROXY"), - "HTTPS_PROXY=" + os.Getenv("HTTPS_PROXY"), - "NO_PROXY=" + os.Getenv("NO_PROXY"), - "JOB_ID=" + jobID, - "PROXY_CACHE=true", - "LOG_RESPONSE_BODY_ON_AUTH_FAILURE=true", - "ACTIONS_ID_TOKEN_REQUEST_TOKEN=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), - "ACTIONS_ID_TOKEN_REQUEST_URL=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"), - }, + Env: proxyEnv(params.ApiUrl), Entrypoint: []string{ "sh", "-c", "update-ca-certificates && /dependabot-proxy", }, @@ -141,6 +132,34 @@ func NewProxy(ctx context.Context, cli *client.Client, params *RunParams, nets * return proxy, nil } +// proxyEnv builds the environment variables passed to the proxy container. +func proxyEnv(apiURL string) []string { + env := []string{ + "HTTP_PROXY=" + os.Getenv("HTTP_PROXY"), + "HTTPS_PROXY=" + os.Getenv("HTTPS_PROXY"), + "NO_PROXY=" + os.Getenv("NO_PROXY"), + "JOB_ID=" + jobID, + "PROXY_CACHE=" + firstNonEmpty(os.Getenv("PROXY_CACHE"), "true"), + "LOG_RESPONSE_BODY_ON_AUTH_FAILURE=true", + "ACTIONS_ID_TOKEN_REQUEST_TOKEN=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), + "ACTIONS_ID_TOKEN_REQUEST_URL=" + os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"), + } + // Only forward JOB_TOKEN and DEPENDABOT_API_URL when JOB_TOKEN is set on the + // host. The proxy uses DEPENDABOT_API_URL to decide which host to inject the + // JOB_TOKEN into as an Authorization header; forwarding it without a token + // (e.g. in the CLI's default/Azure flows) would cause the proxy to clobber + // auth on requests to that host, so the two must be passed together. + if token, ok := os.LookupEnv("JOB_TOKEN"); ok && token != "" { + env = append(env, "JOB_TOKEN="+token) + env = append(env, "DEPENDABOT_API_URL="+apiURL) + } + // Forward OPENSSL_FORCE_FIPS_MODE only when the host has it set. + if fips, ok := os.LookupEnv("OPENSSL_FORCE_FIPS_MODE"); ok { + env = append(env, "OPENSSL_FORCE_FIPS_MODE="+fips) + } + return env +} + func putProxyConfig(ctx context.Context, cli *client.Client, config *Config, id string) error { opt := container.CopyToContainerOptions{} diff --git a/internal/infra/proxy_test.go b/internal/infra/proxy_test.go new file mode 100644 index 00000000..bc614b40 --- /dev/null +++ b/internal/infra/proxy_test.go @@ -0,0 +1,163 @@ +package infra + +import ( + "os" + "strings" + "testing" +) + +func envValue(env []string, key string) (string, bool) { + prefix := key + "=" + for _, e := range env { + if strings.HasPrefix(e, prefix) { + return strings.TrimPrefix(e, prefix), true + } + } + return "", false +} + +func Test_proxyEnv_ProxyCache(t *testing.T) { + t.Run("uses host PROXY_CACHE when set", func(t *testing.T) { + t.Setenv("PROXY_CACHE", "false") + + env := proxyEnv("") + + value, ok := envValue(env, "PROXY_CACHE") + if !ok { + t.Fatal("expected PROXY_CACHE to be present in proxy env") + } + if value != "false" { + t.Errorf("expected PROXY_CACHE to be %q, got %q", "false", value) + } + }) + + t.Run("falls back to true when host PROXY_CACHE is unset", func(t *testing.T) { + t.Setenv("PROXY_CACHE", "placeholder") + os.Unsetenv("PROXY_CACHE") + + env := proxyEnv("") + + value, ok := envValue(env, "PROXY_CACHE") + if !ok { + t.Fatal("expected PROXY_CACHE to be present in proxy env") + } + if value != "true" { + t.Errorf("expected PROXY_CACHE to fall back to %q, got %q", "true", value) + } + }) + + t.Run("falls back to true when host PROXY_CACHE is empty", func(t *testing.T) { + t.Setenv("PROXY_CACHE", "") + + env := proxyEnv("") + + value, ok := envValue(env, "PROXY_CACHE") + if !ok { + t.Fatal("expected PROXY_CACHE to be present in proxy env") + } + if value != "true" { + t.Errorf("expected PROXY_CACHE to fall back to %q, got %q", "true", value) + } + }) +} + +func Test_proxyEnv_OpenSSLForceFIPSMode(t *testing.T) { + t.Run("passes OPENSSL_FORCE_FIPS_MODE from environment", func(t *testing.T) { + t.Setenv("OPENSSL_FORCE_FIPS_MODE", "1") + + env := proxyEnv("") + + value, ok := envValue(env, "OPENSSL_FORCE_FIPS_MODE") + if !ok { + t.Fatal("expected OPENSSL_FORCE_FIPS_MODE to be present in proxy env") + } + if value != "1" { + t.Errorf("expected OPENSSL_FORCE_FIPS_MODE to be %q, got %q", "1", value) + } + }) + + t.Run("omits OPENSSL_FORCE_FIPS_MODE when unset", func(t *testing.T) { + t.Setenv("OPENSSL_FORCE_FIPS_MODE", "placeholder") + os.Unsetenv("OPENSSL_FORCE_FIPS_MODE") + + env := proxyEnv("") + + if _, ok := envValue(env, "OPENSSL_FORCE_FIPS_MODE"); ok { + t.Error("expected OPENSSL_FORCE_FIPS_MODE to be absent from proxy env when host has it unset") + } + }) +} + +func Test_proxyEnv_JobToken(t *testing.T) { + t.Run("passes JOB_TOKEN from environment", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "super-secret-token") + + env := proxyEnv("") + + value, ok := envValue(env, "JOB_TOKEN") + if !ok { + t.Fatal("expected JOB_TOKEN to be present in proxy env") + } + if value != "super-secret-token" { + t.Errorf("expected JOB_TOKEN to be %q, got %q", "super-secret-token", value) + } + }) + + t.Run("omits JOB_TOKEN when unset", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "placeholder") + os.Unsetenv("JOB_TOKEN") + + env := proxyEnv("") + + if _, ok := envValue(env, "JOB_TOKEN"); ok { + t.Error("expected JOB_TOKEN to be absent from proxy env when host has it unset") + } + }) + + t.Run("omits JOB_TOKEN when set to empty", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "") + + env := proxyEnv("") + + if _, ok := envValue(env, "JOB_TOKEN"); ok { + t.Error("expected JOB_TOKEN to be absent from proxy env when host has it empty") + } + }) +} + +func Test_proxyEnv_DependabotAPIURL(t *testing.T) { + t.Run("sets DEPENDABOT_API_URL from the apiURL parameter when JOB_TOKEN is set", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "super-secret-token") + + env := proxyEnv("https://api.example.com") + + value, ok := envValue(env, "DEPENDABOT_API_URL") + if !ok { + t.Fatal("expected DEPENDABOT_API_URL to be present when JOB_TOKEN is set") + } + if value != "https://api.example.com" { + t.Errorf("expected DEPENDABOT_API_URL to be %q, got %q", "https://api.example.com", value) + } + }) + + t.Run("omits DEPENDABOT_API_URL when JOB_TOKEN is unset", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "placeholder") + os.Unsetenv("JOB_TOKEN") + + env := proxyEnv("https://api.example.com") + + if _, ok := envValue(env, "DEPENDABOT_API_URL"); ok { + t.Error("expected DEPENDABOT_API_URL to be absent when JOB_TOKEN is unset") + } + }) + + t.Run("omits DEPENDABOT_API_URL when JOB_TOKEN is empty", func(t *testing.T) { + t.Setenv("JOB_TOKEN", "") + + env := proxyEnv("https://api.example.com") + + if _, ok := envValue(env, "DEPENDABOT_API_URL"); ok { + t.Error("expected DEPENDABOT_API_URL to be absent when JOB_TOKEN is empty") + } + }) +}