From c6f3978ae6c804bf0f885de7c460eae8c820ef63 Mon Sep 17 00:00:00 2001 From: ryank90 Date: Tue, 22 Sep 2026 17:21:08 +0100 Subject: [PATCH] fix(serverless): redact environment values in app JSON output apps show, deploy, and the other app records were printing plaintext environment values in JSON and YAML. The table already hid them. --- docs/runware_serverless_apps_show.md | 7 +++++ internal/cmd/serverless/apps.go | 4 +++ internal/cmd/serverless/display.go | 29 +++++++++++++++++ internal/cmd/serverless/display_test.go | 41 +++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/docs/runware_serverless_apps_show.md b/docs/runware_serverless_apps_show.md index 799bfc1..ffa9b3c 100644 --- a/docs/runware_serverless_apps_show.md +++ b/docs/runware_serverless_apps_show.md @@ -2,6 +2,13 @@ Show details for a serverless application +### Synopsis + +Show details for a serverless application. + +JSON and YAML include environment variable names with each value replaced by +[redacted]. Read a value with 'apps env list'. + ``` runware serverless apps show [flags] ``` diff --git a/internal/cmd/serverless/apps.go b/internal/cmd/serverless/apps.go index 54bc398..539483e 100644 --- a/internal/cmd/serverless/apps.go +++ b/internal/cmd/serverless/apps.go @@ -121,6 +121,10 @@ func newAppsShowCmd(logger *log.Logger) *cobra.Command { return &cobra.Command{ Use: "show ", Short: "Show details for a serverless application", + Long: `Show details for a serverless application. + +JSON and YAML include environment variable names with each value replaced by +[redacted]. Read a value with 'apps env list'.`, Example: ` # show details for an application runware serverless apps show my-app`, Args: cobra.ExactArgs(1), diff --git a/internal/cmd/serverless/display.go b/internal/cmd/serverless/display.go index 7aa0a85..2243027 100644 --- a/internal/cmd/serverless/display.go +++ b/internal/cmd/serverless/display.go @@ -50,6 +50,10 @@ const ( colConcurrency = "Concurrency" ) +// redactedEnvValue replaces a plaintext environment value in JSON and YAML +// app output. apps env list is the command that prints the real value. +const redactedEnvValue = "[redacted]" + // appResult wraps a single app for table/json/yaml display. type appResult serverlessapi.App @@ -80,6 +84,31 @@ func (r appResult) Rows() [][]any { } } +// MarshalJSON redacts plaintext environment values. The table already omits +// them; JSON would otherwise print the value field from the app payload. +func (r appResult) MarshalJSON() ([]byte, error) { + return json.Marshal(r.withRedactedEnv()) +} + +// MarshalYAML redacts plaintext environment values, matching MarshalJSON. +func (r appResult) MarshalYAML() (any, error) { + return r.withRedactedEnv(), nil +} + +func (r appResult) withRedactedEnv() serverlessapi.App { + app := serverlessapi.App(r) + if len(app.EnvironmentVariables) == 0 { + return app + } + env := make([]serverlessapi.EnvironmentVariable, len(app.EnvironmentVariables)) + copy(env, app.EnvironmentVariables) + for i := range env { + env[i].Value = redactedEnvValue + } + app.EnvironmentVariables = env + return app +} + // appsResult wraps an app list for table display. type appsResult []serverlessapi.App diff --git a/internal/cmd/serverless/display_test.go b/internal/cmd/serverless/display_test.go index 1d2c57e..4810e8b 100644 --- a/internal/cmd/serverless/display_test.go +++ b/internal/cmd/serverless/display_test.go @@ -2,11 +2,14 @@ package serverless import ( "bytes" + "encoding/json" "slices" "strings" "testing" "time" + "gopkg.in/yaml.v3" + "github.com/google/uuid" serverlessapi "github.com/runware/runware-cli/internal/api/serverless" "github.com/runware/runware-cli/internal/output" @@ -340,6 +343,44 @@ func TestAppResult_IncludesConfiguration(t *testing.T) { } } +func TestAppResult_RedactsEnvironmentValues(t *testing.T) { + r := appResult{ + AppId: testAppID, + EnvironmentVariables: []serverlessapi.EnvironmentVariable{ + { + Key: testEnvKey, + Value: testEnvValue, + }, + }, + } + + js, err := json.Marshal(r) + if err != nil { + t.Fatalf("json: %v", err) + } + if strings.Contains(string(js), testEnvValue) || !strings.Contains(string(js), redactedEnvValue) { + t.Fatalf("json = %s", js) + } + if !strings.Contains(string(js), testEnvKey) { + t.Fatalf("json dropped the variable name: %s", js) + } + + ym, err := yaml.Marshal(r) + if err != nil { + t.Fatalf("yaml: %v", err) + } + if strings.Contains(string(ym), testEnvValue) || !strings.Contains(string(ym), redactedEnvValue) { + t.Fatalf("yaml = %s", ym) + } + if !strings.Contains(string(ym), testEnvKey) { + t.Fatalf("yaml dropped the variable name: %s", ym) + } + + if r.EnvironmentVariables[0].Value != testEnvValue { + t.Fatalf("redaction mutated the app: %q", r.EnvironmentVariables[0].Value) + } +} + func TestPrintPage_TableWritesNextCursorToErrOut(t *testing.T) { next := "page-2" page := serverlessapi.Page[serverlessapi.App]{