From e08a93a4cd6369d5d616bb643f30419c0c1aa84e Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Thu, 29 May 2025 16:44:00 +0200 Subject: [PATCH 1/3] [AGENT-258] use util function from envutil --- cmd/env.go | 36 ++++++++---------------------------- internal/envutil/envutil.go | 14 ++------------ 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/cmd/env.go b/cmd/env.go index 5859a9f6..4e207441 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -8,7 +8,6 @@ import ( "os" "os/signal" "path/filepath" - "regexp" "strings" "syscall" @@ -37,36 +36,17 @@ Use the subcommands to set, get, list, and delete environment variables and secr } var ( - hasTTY = tui.HasTTY - looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|PASSWORD|sk_`) - isAgentuityEnv = regexp.MustCompile(`(?i)AGENTUITY_`) + hasTTY = tui.HasTTY ) -func descriptionLookingLikeASecret(description string) bool { - if description == "" { - return false - } - val := strings.ToLower(description) - if strings.Contains(val, "secret") { - return true - } - if strings.Contains(val, "password") { - return true - } - if strings.Contains(val, "key") { - return true - } - return false -} - func loadEnvFile(le []env.EnvLineComment, forceSecret bool) (map[string]string, map[string]string) { envs := make(map[string]string) secrets := make(map[string]string) for _, ev := range le { - if isAgentuityEnv.MatchString(ev.Key) { + if envutil.IsAgentuityEnv.MatchString(ev.Key) { continue } - if looksLikeSecret.MatchString(ev.Key) || forceSecret || descriptionLookingLikeASecret(ev.Comment) { + if envutil.LooksLikeSecret.MatchString(ev.Key) || forceSecret || envutil.DescriptionLookingLikeASecret(ev.Comment) { secrets[ev.Key] = ev.Val } else { envs[ev.Key] = ev.Val @@ -79,7 +59,7 @@ func loadOSEnv() map[string]string { osenv := make(map[string]string) for _, line := range os.Environ() { parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 && !isAgentuityEnv.MatchString(parts[0]) { + if len(parts) == 2 && !envutil.IsAgentuityEnv.MatchString(parts[0]) { osenv[parts[0]] = parts[1] } } @@ -152,7 +132,7 @@ Examples: le, _ := env.ParseEnvFile(envfile) var added bool for _, ev := range le { - if !isAgentuityEnv.MatchString(ev.Key) { + if !envutil.IsAgentuityEnv.MatchString(ev.Key) { localenv[ev.Key] = ev.Val added = true } @@ -164,14 +144,14 @@ Examples: if len(args) == 0 && hasEnvFile && len(localenv) > 0 && !hasSetFromFile && !noConfirm { var options []tui.Option for k := range localenv { - if !isAgentuityEnv.MatchString(k) { + if !envutil.IsAgentuityEnv.MatchString(k) { options = append(options, tui.Option{ID: k, Text: k, Selected: true}) } } results := tui.MultiSelect(logger, "Set environment variables from .env", "", options) for _, result := range results { val := localenv[result] - if looksLikeSecret.MatchString(result) || forceSecret { + if envutil.LooksLikeSecret.MatchString(result) || forceSecret { secrets[result] = val } else { envs[result] = val @@ -206,7 +186,7 @@ Examples: askMore = false } } - isSecret = looksLikeSecret.MatchString(key) || forceSecret + isSecret = envutil.LooksLikeSecret.MatchString(key) || forceSecret if key != "" && value == "" && !noConfirm { if len(envs) == 0 && len(secrets) == 0 { fi, _ := os.Stdin.Stat() diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index 9d3cc0f9..a51eacb6 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -27,7 +27,7 @@ var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990 var LooksLikeSecret = looksLikeSecret var IsAgentuityEnv = isAgentuityEnv -var looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|PASSWORD|sk_`) +var looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|CREDENTIAL|PASSWORD|sk_`) var isAgentuityEnv = regexp.MustCompile(`(?i)AGENTUITY_`) // ProcessEnvFiles handles .env and template env processing @@ -266,17 +266,7 @@ func DescriptionLookingLikeASecret(description string) bool { if description == "" { return false } - val := strings.ToLower(description) - if strings.Contains(val, "secret") { - return true - } - if strings.Contains(val, "password") { - return true - } - if strings.Contains(val, "key") { - return true - } - return false + return looksLikeSecret.MatchString(description) } // LoadOSEnv loads the OS environment variables From 3474d1789f288342c30eb14c34b4dc4e5b32f17a Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Thu, 29 May 2025 22:15:11 +0200 Subject: [PATCH 2/3] secrets! --- internal/envutil/envutil.go | 13 ++++-- internal/envutil/envutil_test.go | 73 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 internal/envutil/envutil_test.go diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index a51eacb6..e008e296 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -181,10 +181,17 @@ func HandleMissingProjectEnvs(ctx context.Context, logger logger.Logger, le []en } if force { for key, val := range keyvalue { - if projectData.Env == nil { - projectData.Env = make(map[string]string) + if looksLikeSecret.MatchString(key) { + if projectData.Secrets == nil { + projectData.Secrets = make(map[string]string) + } + projectData.Secrets[key] = cstr.Mask(val) + } else { + if projectData.Env == nil { + projectData.Env = make(map[string]string) + } + projectData.Env[key] = val } - projectData.Env[key] = val } _, err := theproject.SetProjectEnv(ctx, logger, apiUrl, token, projectData.Env, projectData.Secrets) if err != nil { diff --git a/internal/envutil/envutil_test.go b/internal/envutil/envutil_test.go new file mode 100644 index 00000000..407e4ba0 --- /dev/null +++ b/internal/envutil/envutil_test.go @@ -0,0 +1,73 @@ +package envutil + +import ( + "testing" +) + +func TestLooksLikeSecret(t *testing.T) { + tests := []struct { + input string + matches bool + }{ + {"API_KEY", true}, + {"SECRET_TOKEN", true}, + {"MY_PASSWORD", true}, + {"CREDENTIALS", true}, + {"sk_test_123", true}, + {"ACCESS_TOKEN", true}, + {"DATABASE_URL", false}, + {"USERNAME", false}, + {"EMAIL", false}, + {"AGENTUITY_SECRET", true}, // should match because of SECRET + {"SOME_RANDOM_VAR", false}, + + {"api_key", true}, + {"secret_token", true}, + {"my_password", true}, + {"credentials", true}, + {"sk_test_123", true}, + {"access_token", true}, + {"database_url", false}, + {"username", false}, + {"email", false}, + {"agentuity_secret", true}, + {"some_random_var", false}, + } + + for _, tt := range tests { + if LooksLikeSecret.MatchString(tt.input) != tt.matches { + t.Errorf("LooksLikeSecret.MatchString(%q) = %v, want %v", tt.input, LooksLikeSecret.MatchString(tt.input), tt.matches) + } + } +} + +func TestIsAgentuityEnv(t *testing.T) { + tests := []struct { + input string + matches bool + }{ + {"AGENTUITY_API_KEY", true}, + {"AGENTUITY_SECRET", true}, + {"AGENTUITY_TOKEN", true}, + {"AGENTUITY_SOMETHING", true}, + {"SOME_AGENTUITY_VAR", true}, + {"API_KEY", false}, + {"SECRET", false}, + {"DATABASE_URL", false}, + + {"agentuity_api_key", true}, + {"agentuity_secret", true}, + {"agentuity_token", true}, + {"agentuity_something", true}, + {"some_agentuity_var", true}, + {"api_key", false}, + {"secret", false}, + {"database_url", false}, + } + + for _, tt := range tests { + if IsAgentuityEnv.MatchString(tt.input) != tt.matches { + t.Errorf("IsAgentuityEnv.MatchString(%q) = %v, want %v", tt.input, IsAgentuityEnv.MatchString(tt.input), tt.matches) + } + } +} From 1c04855e7167114cdddff5ccc7768aceb738b779 Mon Sep 17 00:00:00 2001 From: Pedro Enrique Date: Thu, 29 May 2025 22:44:34 +0200 Subject: [PATCH 3/3] bettet regex --- internal/envutil/envutil.go | 2 +- internal/envutil/envutil_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/envutil/envutil.go b/internal/envutil/envutil.go index e008e296..ca54a127 100644 --- a/internal/envutil/envutil.go +++ b/internal/envutil/envutil.go @@ -27,7 +27,7 @@ var redDiff = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#990 var LooksLikeSecret = looksLikeSecret var IsAgentuityEnv = isAgentuityEnv -var looksLikeSecret = regexp.MustCompile(`(?i)KEY|SECRET|TOKEN|CREDENTIAL|PASSWORD|sk_`) +var looksLikeSecret = regexp.MustCompile(`(?i)(^|_|-)(APIKEY|API_KEY|PRIVATE_KEY|KEY|SECRET|TOKEN|CREDENTIAL|CREDENTIALS|PASSWORD|sk_[a-zA-Z0-9_-]*|BEARER|AUTH|JWT|WEBHOOK)($|_|-)`) var isAgentuityEnv = regexp.MustCompile(`(?i)AGENTUITY_`) // ProcessEnvFiles handles .env and template env processing diff --git a/internal/envutil/envutil_test.go b/internal/envutil/envutil_test.go index 407e4ba0..ab3c8bb6 100644 --- a/internal/envutil/envutil_test.go +++ b/internal/envutil/envutil_test.go @@ -20,6 +20,11 @@ func TestLooksLikeSecret(t *testing.T) { {"EMAIL", false}, {"AGENTUITY_SECRET", true}, // should match because of SECRET {"SOME_RANDOM_VAR", false}, + {"PRIVATE_KEY", true}, + {"MY_APIKEY", true}, + {"MY_API_KEY", true}, + {"MY_API-KEY", true}, + {"MONKEY", false}, {"api_key", true}, {"secret_token", true},