From cae35a179d5c5f03a57da2c166d0fc517eca951c Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 23 Jul 2026 16:49:36 +0100 Subject: [PATCH] Redact container env vars in logs Entire-Checkpoint: 01KY7V1VPA7BTC8YG9XWSA4DC4 --- internal/profiles/profiles.go | 2 +- internal/runners/docker/run.go | 2 +- internal/runners/podman/run.go | 2 +- internal/runners/util/container/redact.go | 41 ++++++++++++ .../runners/util/container/redact_test.go | 64 +++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 internal/runners/util/container/redact.go create mode 100644 internal/runners/util/container/redact_test.go diff --git a/internal/profiles/profiles.go b/internal/profiles/profiles.go index 1be1c1f..42b697c 100644 --- a/internal/profiles/profiles.go +++ b/internal/profiles/profiles.go @@ -670,7 +670,7 @@ func createNewDisplay(bin string, ca, cert, key []byte, profile *types.Profile, grabberShortcut()) } - slog.Debug("exec", "binary", bin, "args", dockerArgs) + slog.Debug("exec", "binary", bin, "args", container.RedactEnvArgs(dockerArgs)) cmd := execabs.Command(bin, dockerArgs...) cmd.Env = append(cmd.Env, os.Environ()...) diff --git a/internal/runners/docker/run.go b/internal/runners/docker/run.go index 271303a..920775f 100644 --- a/internal/runners/docker/run.go +++ b/internal/runners/docker/run.go @@ -266,7 +266,7 @@ func Run(ew types.EffectiveWorkload) error { args = append(args, wl.Command) args = append(args, wl.Args...) - slog.Debug("exec", "binary", runnerBinary, "args", args) + slog.Debug("exec", "binary", runnerBinary, "args", container.RedactEnvArgs(args)) cmd := execabs.Command(runnerBinary, args...) if ew.Workload.HostAccess.Mime { diff --git a/internal/runners/podman/run.go b/internal/runners/podman/run.go index 66c5735..e364ee7 100644 --- a/internal/runners/podman/run.go +++ b/internal/runners/podman/run.go @@ -254,7 +254,7 @@ func Run(ew types.EffectiveWorkload) error { args = append(args, wl.Command) args = append(args, wl.Args...) - slog.Debug("exec", "binary", runnerBinary, "args", args) + slog.Debug("exec", "binary", runnerBinary, "args", container.RedactEnvArgs(args)) cmd := execabs.Command(runnerBinary, args...) if ew.Workload.HostAccess.Mime { diff --git a/internal/runners/util/container/redact.go b/internal/runners/util/container/redact.go new file mode 100644 index 0000000..e5ff080 --- /dev/null +++ b/internal/runners/util/container/redact.go @@ -0,0 +1,41 @@ +package container + +import "strings" + +const redactedEnvValue = "REDACTED" + +// RedactEnvArgs returns a copy of container runner arguments with explicit +// environment variable values removed. The original arguments are left +// untouched so callers can safely use the result for logging and the original +// slice for execution. +func RedactEnvArgs(args []string) []string { + redacted := append([]string(nil), args...) + + for i := 0; i < len(redacted); i++ { + switch redacted[i] { + case "-e", "--env": + if i+1 < len(redacted) { + redacted[i+1] = redactEnvSpec(redacted[i+1]) + i++ + } + default: + for _, prefix := range []string{"-e=", "--env="} { + if spec, ok := strings.CutPrefix(redacted[i], prefix); ok { + redacted[i] = prefix + redactEnvSpec(spec) + break + } + } + } + } + + return redacted +} + +func redactEnvSpec(spec string) string { + name, _, ok := strings.Cut(spec, "=") + if !ok { + return spec + } + + return name + "=" + redactedEnvValue +} diff --git a/internal/runners/util/container/redact_test.go b/internal/runners/util/container/redact_test.go new file mode 100644 index 0000000..f5be3a6 --- /dev/null +++ b/internal/runners/util/container/redact_test.go @@ -0,0 +1,64 @@ +package container + +import ( + "reflect" + "testing" +) + +func TestRedactEnvArgs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + args []string + want []string + }{ + { + name: "short flag with separate value", + args: []string{"run", "-e", "TOKEN=secret", "image"}, + want: []string{"run", "-e", "TOKEN=REDACTED", "image"}, + }, + { + name: "long flag with separate value", + args: []string{"run", "--env", "PASSWORD=two=parts", "image"}, + want: []string{"run", "--env", "PASSWORD=REDACTED", "image"}, + }, + { + name: "short flag with inline value", + args: []string{"run", "-e=API_KEY=secret", "image"}, + want: []string{"run", "-e=API_KEY=REDACTED", "image"}, + }, + { + name: "long flag with inline value", + args: []string{"run", "--env=CLIENT_SECRET=secret", "image"}, + want: []string{"run", "--env=CLIENT_SECRET=REDACTED", "image"}, + }, + { + name: "inherited environment variable", + args: []string{"run", "-e", "TOKEN", "--env=PASSWORD", "image"}, + want: []string{"run", "-e", "TOKEN", "--env=PASSWORD", "image"}, + }, + { + name: "unrelated arguments", + args: []string{"run", "--entrypoint=/bin/env", "image", "TOKEN=secret"}, + want: []string{"run", "--entrypoint=/bin/env", "image", "TOKEN=secret"}, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + original := append([]string(nil), tc.args...) + got := RedactEnvArgs(tc.args) + + if !reflect.DeepEqual(got, tc.want) { + t.Fatalf("RedactEnvArgs() = %q, want %q", got, tc.want) + } + if !reflect.DeepEqual(tc.args, original) { + t.Fatalf("RedactEnvArgs() mutated input: got %q, want %q", tc.args, original) + } + }) + } +}