Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 8 additions & 28 deletions cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"os"
"os/signal"
"path/filepath"
"regexp"
"strings"
"syscall"

Expand Down Expand Up @@ -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
Expand All @@ -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]
}
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
27 changes: 12 additions & 15 deletions internal/envutil/envutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)(^|_|-)(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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -266,17 +273,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
Expand Down
78 changes: 78 additions & 0 deletions internal/envutil/envutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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},
{"PRIVATE_KEY", true},
{"MY_APIKEY", true},
{"MY_API_KEY", true},
{"MY_API-KEY", true},
{"MONKEY", 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)
}
}
}
Loading