diff --git a/agent/agent.go b/agent/agent.go index 132aeb6a12693..550392802df5a 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -2291,12 +2291,34 @@ func (a *agent) HandleHTTPDebugManifest(w http.ResponseWriter, r *http.Request) return } + // Redact env values. This endpoint is unauthenticated on loopback, + // reachable by any process regardless of Unix user. Keys are preserved + // so operators can still see which variables are configured. + debugManifest := *sdkManifest + if len(sdkManifest.EnvironmentVariables) > 0 { + envs := make(map[string]string, len(sdkManifest.EnvironmentVariables)) + for k, v := range sdkManifest.EnvironmentVariables { + // Preserve empty values, which carry no secret, matching + // sanitizeEnv in support/support.go. + if v == "" { + envs[k] = v + continue + } + envs[k] = redactedManifestEnvValue + } + debugManifest.EnvironmentVariables = envs + } + w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(sdkManifest); err != nil { + if err := json.NewEncoder(w).Encode(debugManifest); err != nil { a.logger.Error(a.hardCtx, "write debug manifest", slog.Error(err)) } } +// redactedManifestEnvValue matches the marker used by sanitizeEnv in +// support/support.go so a support bundle and this endpoint agree. +const redactedManifestEnvValue = "***REDACTED***" + func (a *agent) HTTPDebug() http.Handler { r := chi.NewRouter() diff --git a/agent/agent_test.go b/agent/agent_test.go index 6f9273779512c..9fbe263fafd49 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -3672,6 +3672,10 @@ func TestAgent_DebugServer(t *testing.T) { //nolint:dogsled conn, _, _, _, agnt := setupAgentWithSecrets(t, agentsdk.Manifest{ DERPMap: derpMap, + EnvironmentVariables: map[string]string{ + "AWS_SECRET_ACCESS_KEY": "env-value-should-be-redacted-67890", + "EMPTY_VAR": "", + }, }, []agentsdk.WorkspaceSecret{ {EnvName: "DEBUG_SECRET", Value: []byte("super-secret-value-12345")}, }, 0, func(c *agenttest.Client, o *agent.Options) { @@ -3800,6 +3804,34 @@ func TestAgent_DebugServer(t *testing.T) { require.NoError(t, json.Unmarshal(body, &v)) }) + t.Run("ManifestEnvVarValuesRedacted", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/manifest", nil) + require.NoError(t, err) + + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + + require.NotContains(t, string(body), "env-value-should-be-redacted-67890") + + var v agentsdk.Manifest + require.NoError(t, json.Unmarshal(body, &v)) + + require.Contains(t, v.EnvironmentVariables, "AWS_SECRET_ACCESS_KEY") + require.Equal(t, "***REDACTED***", v.EnvironmentVariables["AWS_SECRET_ACCESS_KEY"]) + + // Empty values carry no secret and are preserved as empty. + require.Contains(t, v.EnvironmentVariables, "EMPTY_VAR") + require.Equal(t, "", v.EnvironmentVariables["EMPTY_VAR"]) + }) + t.Run("Logs", func(t *testing.T) { t.Parallel()