From 6ee3e899b39dd1c644ade03ee5e176bd5aeef714 Mon Sep 17 00:00:00 2001 From: saif <11242541+saifsmailbox98@users.noreply.github.com> Date: Sun, 19 Jul 2026 04:41:19 +0530 Subject: [PATCH 1/3] feat(agent-proxy): resolve connect config from flag, env, and .infisical.json --- packages/cmd/agent_proxy.go | 43 ++++---- packages/models/cli.go | 2 + packages/util/constants.go | 8 +- packages/util/helper.go | 71 ++++++++++++++ packages/util/resolve_test.go | 180 ++++++++++++++++++++++++++++++++++ packages/util/secrets.go | 18 ++++ 6 files changed, 296 insertions(+), 26 deletions(-) create mode 100644 packages/util/resolve_test.go diff --git a/packages/cmd/agent_proxy.go b/packages/cmd/agent_proxy.go index a8fefb01..2d5323f4 100644 --- a/packages/cmd/agent_proxy.go +++ b/packages/cmd/agent_proxy.go @@ -29,9 +29,13 @@ var agentProxyCmd = &cobra.Command{ } var agentProxyConnectCmd = &cobra.Command{ - Use: "connect [flags] -- [agent start command]", - Short: "Set up the environment and launch an agent behind the agent proxy", - Example: "infisical secrets agent-proxy connect --proxy=:17322 --projectId= --env=prod --path=/myapp -- claude", + Use: "connect [flags] -- [agent start command]", + Short: "Set up the environment and launch an agent behind the agent proxy", + Example: `# With flags +infisical secrets agent-proxy connect --proxy=:17322 --projectId= --env=prod --path=/myapp -- claude + +# With environment variables (INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT, INFISICAL_SECRET_PATH, INFISICAL_AGENT_PROXY_ADDRESS) +infisical secrets agent-proxy connect -- claude`, DisableFlagsInUseLine: true, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { @@ -103,28 +107,17 @@ func mergeNoProxy(operatorEntries ...string) string { } func runAgentProxyConnect(cmd *cobra.Command, args []string) { - proxyAddr, err := cmd.Flags().GetString("proxy") - if err != nil || proxyAddr == "" { - util.HandleError(fmt.Errorf("the --proxy flag is required (e.g. --proxy=:17322)")) + proxyAddr := util.ResolveAgentProxyAddress(cmd) + if proxyAddr == "" { + util.HandleError(fmt.Errorf("the agent proxy address is required; pass --proxy, set INFISICAL_AGENT_PROXY_ADDRESS, or set agentProxyAddress in .infisical.json (e.g. :17322)")) } - environment, err := cmd.Flags().GetString("env") - if err != nil { - util.HandleError(err, "Unable to parse --env") - } - if !cmd.Flags().Changed("env") { - if envFromWorkspace := util.GetEnvFromWorkspaceFile(); envFromWorkspace != "" { - environment = envFromWorkspace - } - } + environment := util.ResolveEnvironmentName(cmd) if environment == "" { - util.HandleError(fmt.Errorf("the --env flag is required")) + util.HandleError(fmt.Errorf("the environment is required; pass --env, set INFISICAL_ENVIRONMENT, or set defaultEnvironment in .infisical.json")) } - secretPath, err := cmd.Flags().GetString("path") - if err != nil { - util.HandleError(err, "Unable to parse --path") - } + secretPath := util.ResolveSecretPath(cmd) projectID, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "projectId", []string{util.INFISICAL_PROJECT_ID_NAME}, "") if err != nil { @@ -156,7 +149,7 @@ func runAgentProxyConnect(cmd *cobra.Command, args []string) { realSecrets := fetchAgentRealSecrets(token, projectID, environment, secretPath) - allowReadableBrokered, _ := cmd.Flags().GetBool("allow-readable-brokered-secrets") + allowReadableBrokered := util.GetBoolFlagOrEnv(cmd, "allow-readable-brokered-secrets", util.INFISICAL_AGENT_PROXY_ALLOW_READABLE_BROKERED_SECRETS_NAME) if !allowReadableBrokered { // static readability is derived from realSecrets we already fetch; dynamic lease-ability comes from // the server (callerCanLease) since we don't fetch dynamic secrets here. @@ -436,15 +429,15 @@ func runAgentProcess(args, env []string) error { } func init() { - agentProxyConnectCmd.Flags().String("proxy", "", "address of the agent proxy (host:port)") - agentProxyConnectCmd.Flags().StringP("env", "e", "", "environment slug to fetch proxied services and secrets from") - agentProxyConnectCmd.Flags().String("path", "/", "secret path (folder) scope") + agentProxyConnectCmd.Flags().String("proxy", "", "address of the agent proxy as host:port (falls back to INFISICAL_AGENT_PROXY_ADDRESS or agentProxyAddress in .infisical.json)") + agentProxyConnectCmd.Flags().StringP("env", "e", "", "environment slug to fetch proxied services and secrets from (falls back to INFISICAL_ENVIRONMENT or .infisical.json)") + agentProxyConnectCmd.Flags().String("path", "/", "secret path (folder) scope (falls back to INFISICAL_SECRET_PATH or defaultSecretPath in .infisical.json)") agentProxyConnectCmd.Flags().String("projectId", "", "project id (falls back to INFISICAL_PROJECT_ID or .infisical.json)") agentProxyConnectCmd.Flags().String("client-id", "", "universal auth client id for the agent machine identity") agentProxyConnectCmd.Flags().String("client-secret", "", "universal auth client secret for the agent machine identity") agentProxyConnectCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token") agentProxyConnectCmd.Flags().String("no-proxy", "", "additional comma-separated hosts to bypass the proxy (always merged with localhost,127.0.0.1)") - agentProxyConnectCmd.Flags().Bool("allow-readable-brokered-secrets", false, "start even if the agent can read secrets that proxied services broker to it (bypasses a misconfiguration guardrail)") + agentProxyConnectCmd.Flags().Bool("allow-readable-brokered-secrets", false, "start even if the agent can read secrets that proxied services broker to it (bypasses a misconfiguration guardrail; falls back to INFISICAL_AGENT_PROXY_ALLOW_READABLE_BROKERED_SECRETS)") agentProxyStartCmd.Flags().Int("port", 17322, "port for the agent proxy to listen on") agentProxyStartCmd.Flags().String("unmatched-host", "allow", "policy for hosts with no proxied service: allow | block") diff --git a/packages/models/cli.go b/packages/models/cli.go index a739d63c..97c46038 100644 --- a/packages/models/cli.go +++ b/packages/models/cli.go @@ -112,6 +112,8 @@ type WorkspaceConfigFile struct { WorkspaceId string `json:"workspaceId"` DefaultEnvironment string `json:"defaultEnvironment"` GitBranchToEnvironmentMapping map[string]string `json:"gitBranchToEnvironmentMapping"` + DefaultSecretPath string `json:"defaultSecretPath,omitempty"` + AgentProxyAddress string `json:"agentProxyAddress,omitempty"` Domain string `json:"domain,omitempty"` } diff --git a/packages/util/constants.go b/packages/util/constants.go index 50300e85..764ad3ca 100644 --- a/packages/util/constants.go +++ b/packages/util/constants.go @@ -8,8 +8,14 @@ const ( INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json" INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN" INFISICAL_PROJECT_ID_NAME = "INFISICAL_PROJECT_ID" + INFISICAL_ENVIRONMENT_NAME = "INFISICAL_ENVIRONMENT" + INFISICAL_SECRET_PATH_NAME = "INFISICAL_SECRET_PATH" INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME = "INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN" - INFISICAL_VAULT_FILE_PASSPHRASE_ENV_NAME = "INFISICAL_VAULT_FILE_PASSPHRASE" // This works because we've forked the keyring package and added support for this env variable. This explains why you won't find any occurrences of it in the CLI codebase. + + // Agent proxy (connect) + INFISICAL_AGENT_PROXY_ADDRESS_NAME = "INFISICAL_AGENT_PROXY_ADDRESS" + INFISICAL_AGENT_PROXY_ALLOW_READABLE_BROKERED_SECRETS_NAME = "INFISICAL_AGENT_PROXY_ALLOW_READABLE_BROKERED_SECRETS" + INFISICAL_VAULT_FILE_PASSPHRASE_ENV_NAME = "INFISICAL_VAULT_FILE_PASSPHRASE" // This works because we've forked the keyring package and added support for this env variable. This explains why you won't find any occurrences of it in the CLI codebase. INFISICAL_BOOTSTRAP_EMAIL_NAME = "INFISICAL_ADMIN_EMAIL" INFISICAL_BOOTSTRAP_PASSWORD_NAME = "INFISICAL_ADMIN_PASSWORD" diff --git a/packages/util/helper.go b/packages/util/helper.go index a1dae4c4..daab88e1 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -557,6 +557,77 @@ func GetCmdFlagOrEnvWithDefaultValue(cmd *cobra.Command, flag string, envNames [ return value, nil } +// ResolveEnvironmentName resolves the environment slug for a command, in order: +// the --env flag (if explicitly set) > INFISICAL_ENVIRONMENT > .infisical.json +// (git-branch mapping, then defaultEnvironment) > the flag's own default value. +// These flags carry non-empty defaults (e.g. "dev"), so GetCmdFlagOrEnvWithDefaultValue +// cannot be used here: its empty-string check would never consult env/workspace. +func ResolveEnvironmentName(cmd *cobra.Command) string { + if cmd.Flags().Changed("env") { + value, _ := cmd.Flags().GetString("env") + return value + } + if value := strings.TrimSpace(os.Getenv(INFISICAL_ENVIRONMENT_NAME)); value != "" { + return value + } + if value := GetEnvFromWorkspaceFile(); value != "" { + return value + } + value, _ := cmd.Flags().GetString("env") + return value +} + +// ResolveSecretPath resolves the secret path (folder) for a command, in order: +// the --path flag (if explicitly set) > INFISICAL_SECRET_PATH > .infisical.json +// defaultSecretPath > the flag's own default value ("/"). +func ResolveSecretPath(cmd *cobra.Command) string { + if cmd.Flags().Changed("path") { + value, _ := cmd.Flags().GetString("path") + return value + } + if value := strings.TrimSpace(os.Getenv(INFISICAL_SECRET_PATH_NAME)); value != "" { + return value + } + if value := GetSecretPathFromWorkspaceFile(); value != "" { + return value + } + value, _ := cmd.Flags().GetString("path") + return value +} + +// ResolveAgentProxyAddress resolves the agent proxy address for `agent-proxy connect`, in order: +// the --proxy flag (if explicitly set) > INFISICAL_AGENT_PROXY_ADDRESS > .infisical.json +// agentProxyAddress > empty (the caller requires a non-empty result). +func ResolveAgentProxyAddress(cmd *cobra.Command) string { + if cmd.Flags().Changed("proxy") { + value, _ := cmd.Flags().GetString("proxy") + return value + } + if value := strings.TrimSpace(os.Getenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME)); value != "" { + return value + } + return GetAgentProxyAddressFromWorkspaceFile() +} + +// GetBoolFlagOrEnv resolves a boolean flag from the flag (if explicitly set), then the given +// environment variable, then the flag's default. An unparseable env value fails closed to false. +func GetBoolFlagOrEnv(cmd *cobra.Command, flag string, envName string) bool { + if cmd.Flags().Changed(flag) { + value, _ := cmd.Flags().GetBool(flag) + return value + } + if raw := strings.TrimSpace(os.Getenv(envName)); raw != "" { + parsed, err := strconv.ParseBool(raw) + if err != nil { + log.Warn().Msgf("Ignoring %s: %q is not a valid boolean", envName, raw) + return false + } + return parsed + } + value, _ := cmd.Flags().GetBool(flag) + return value +} + func GenerateRandomString(length int) string { b := make([]byte, length) for i := range b { diff --git a/packages/util/resolve_test.go b/packages/util/resolve_test.go new file mode 100644 index 00000000..da6fcfb1 --- /dev/null +++ b/packages/util/resolve_test.go @@ -0,0 +1,180 @@ +package util + +import ( + "os" + "path/filepath" + "testing" + + "github.com/spf13/cobra" +) + +// newResolveTestCmd builds a command with the flags the resolvers read. Passing a non-empty +// value marks that flag as explicitly set (Changed == true), mirroring a user-passed flag. +func newResolveTestCmd(t *testing.T) *cobra.Command { + t.Helper() + cmd := &cobra.Command{Use: "test"} + cmd.Flags().String("proxy", "", "") + cmd.Flags().StringP("env", "e", "dev", "") + cmd.Flags().String("path", "/", "") + cmd.Flags().Bool("allow", false, "") + return cmd +} + +// writeWorkspace drops a .infisical.json into an isolated cwd so file-fallback is deterministic. +func writeWorkspace(t *testing.T, contents string) { + t.Helper() + dir := t.TempDir() + t.Chdir(dir) + if err := os.WriteFile(filepath.Join(dir, ".infisical.json"), []byte(contents), 0o600); err != nil { + t.Fatalf("write workspace: %v", err) + } +} + +func TestResolveEnvironmentName(t *testing.T) { + t.Run("flag wins over env and file", func(t *testing.T) { + writeWorkspace(t, `{"defaultEnvironment":"fromfile"}`) + t.Setenv(INFISICAL_ENVIRONMENT_NAME, "fromenv") + cmd := newResolveTestCmd(t) + _ = cmd.Flags().Set("env", "fromflag") + if got := ResolveEnvironmentName(cmd); got != "fromflag" { + t.Fatalf("got %q, want fromflag", got) + } + }) + + t.Run("env wins over file when flag unset", func(t *testing.T) { + writeWorkspace(t, `{"defaultEnvironment":"fromfile"}`) + t.Setenv(INFISICAL_ENVIRONMENT_NAME, "fromenv") + if got := ResolveEnvironmentName(newResolveTestCmd(t)); got != "fromenv" { + t.Fatalf("got %q, want fromenv", got) + } + }) + + t.Run("file used when flag and env unset", func(t *testing.T) { + writeWorkspace(t, `{"defaultEnvironment":"fromfile"}`) + t.Setenv(INFISICAL_ENVIRONMENT_NAME, "") + if got := ResolveEnvironmentName(newResolveTestCmd(t)); got != "fromfile" { + t.Fatalf("got %q, want fromfile", got) + } + }) + + t.Run("flag default is the final fallback", func(t *testing.T) { + t.Chdir(t.TempDir()) // no workspace file + t.Setenv(INFISICAL_ENVIRONMENT_NAME, "") + if got := ResolveEnvironmentName(newResolveTestCmd(t)); got != "dev" { + t.Fatalf("got %q, want dev", got) + } + }) +} + +func TestResolveSecretPath(t *testing.T) { + t.Run("flag wins", func(t *testing.T) { + writeWorkspace(t, `{"defaultSecretPath":"/fromfile"}`) + t.Setenv(INFISICAL_SECRET_PATH_NAME, "/fromenv") + cmd := newResolveTestCmd(t) + _ = cmd.Flags().Set("path", "/fromflag") + if got := ResolveSecretPath(cmd); got != "/fromflag" { + t.Fatalf("got %q, want /fromflag", got) + } + }) + + t.Run("env over file", func(t *testing.T) { + writeWorkspace(t, `{"defaultSecretPath":"/fromfile"}`) + t.Setenv(INFISICAL_SECRET_PATH_NAME, "/fromenv") + if got := ResolveSecretPath(newResolveTestCmd(t)); got != "/fromenv" { + t.Fatalf("got %q, want /fromenv", got) + } + }) + + t.Run("file when flag and env unset", func(t *testing.T) { + writeWorkspace(t, `{"defaultSecretPath":"/fromfile"}`) + t.Setenv(INFISICAL_SECRET_PATH_NAME, "") + if got := ResolveSecretPath(newResolveTestCmd(t)); got != "/fromfile" { + t.Fatalf("got %q, want /fromfile", got) + } + }) + + t.Run("defaults to /", func(t *testing.T) { + t.Chdir(t.TempDir()) + t.Setenv(INFISICAL_SECRET_PATH_NAME, "") + if got := ResolveSecretPath(newResolveTestCmd(t)); got != "/" { + t.Fatalf("got %q, want /", got) + } + }) +} + +func TestResolveAgentProxyAddress(t *testing.T) { + t.Run("flag wins", func(t *testing.T) { + writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) + t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "env:1") + cmd := newResolveTestCmd(t) + _ = cmd.Flags().Set("proxy", "flag:1") + if got := ResolveAgentProxyAddress(cmd); got != "flag:1" { + t.Fatalf("got %q, want flag:1", got) + } + }) + + t.Run("env over file", func(t *testing.T) { + writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) + t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "env:1") + if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "env:1" { + t.Fatalf("got %q, want env:1", got) + } + }) + + t.Run("file when flag and env unset", func(t *testing.T) { + writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) + t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "") + if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "file:1" { + t.Fatalf("got %q, want file:1", got) + } + }) + + t.Run("empty when nothing set", func(t *testing.T) { + t.Chdir(t.TempDir()) + t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "") + if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "" { + t.Fatalf("got %q, want empty", got) + } + }) +} + +func TestGetBoolFlagOrEnv(t *testing.T) { + const env = "INFISICAL_TEST_BOOL" + + t.Run("explicit flag wins", func(t *testing.T) { + t.Setenv(env, "false") + cmd := newResolveTestCmd(t) + _ = cmd.Flags().Set("allow", "true") + if !GetBoolFlagOrEnv(cmd, "allow", env) { + t.Fatal("expected true from flag") + } + }) + + t.Run("env true when flag unset", func(t *testing.T) { + t.Setenv(env, "true") + if !GetBoolFlagOrEnv(newResolveTestCmd(t), "allow", env) { + t.Fatal("expected true from env") + } + }) + + t.Run("env false when flag unset", func(t *testing.T) { + t.Setenv(env, "false") + if GetBoolFlagOrEnv(newResolveTestCmd(t), "allow", env) { + t.Fatal("expected false from env") + } + }) + + t.Run("unparseable env fails closed to false", func(t *testing.T) { + t.Setenv(env, "yeah") + if GetBoolFlagOrEnv(newResolveTestCmd(t), "allow", env) { + t.Fatal("expected false on unparseable env") + } + }) + + t.Run("flag default when nothing set", func(t *testing.T) { + t.Setenv(env, "") + if GetBoolFlagOrEnv(newResolveTestCmd(t), "allow", env) { + t.Fatal("expected false (flag default)") + } + }) +} diff --git a/packages/util/secrets.go b/packages/util/secrets.go index 5ad4338c..4dd02938 100644 --- a/packages/util/secrets.go +++ b/packages/util/secrets.go @@ -502,6 +502,24 @@ func GetEnvFromWorkspaceFile() string { return workspaceFile.DefaultEnvironment } +func GetSecretPathFromWorkspaceFile() string { + workspaceFile, err := GetWorkSpaceFromFile() + if err != nil { + log.Debug().Msgf("GetSecretPathFromWorkspaceFile: [err=%s]", err) + return "" + } + return workspaceFile.DefaultSecretPath +} + +func GetAgentProxyAddressFromWorkspaceFile() string { + workspaceFile, err := GetWorkSpaceFromFile() + if err != nil { + log.Debug().Msgf("GetAgentProxyAddressFromWorkspaceFile: [err=%s]", err) + return "" + } + return workspaceFile.AgentProxyAddress +} + func GetEnvelopmentBasedOnGitBranch(workspaceFile models.WorkspaceConfigFile) string { branch, err := getCurrentBranch() if err != nil { From d2df451f0aad0c94ff642663487e6b3e2c9c552a Mon Sep 17 00:00:00 2001 From: saif <11242541+saifsmailbox98@users.noreply.github.com> Date: Sun, 19 Jul 2026 05:30:55 +0530 Subject: [PATCH 2/3] test(agent-proxy): mirror connect's empty --env default; fix resolver doc comment --- packages/util/helper.go | 6 +++--- packages/util/resolve_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/util/helper.go b/packages/util/helper.go index daab88e1..5500216c 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -557,11 +557,11 @@ func GetCmdFlagOrEnvWithDefaultValue(cmd *cobra.Command, flag string, envNames [ return value, nil } -// ResolveEnvironmentName resolves the environment slug for a command, in order: +// ResolveEnvironmentName resolves the environment slug for `agent-proxy connect`, in order: // the --env flag (if explicitly set) > INFISICAL_ENVIRONMENT > .infisical.json // (git-branch mapping, then defaultEnvironment) > the flag's own default value. -// These flags carry non-empty defaults (e.g. "dev"), so GetCmdFlagOrEnvWithDefaultValue -// cannot be used here: its empty-string check would never consult env/workspace. +// It keys off cmd.Flags().Changed rather than an empty-value check so env and workspace +// are still consulted when --env is left at its default. func ResolveEnvironmentName(cmd *cobra.Command) string { if cmd.Flags().Changed("env") { value, _ := cmd.Flags().GetString("env") diff --git a/packages/util/resolve_test.go b/packages/util/resolve_test.go index da6fcfb1..3af1727d 100644 --- a/packages/util/resolve_test.go +++ b/packages/util/resolve_test.go @@ -14,7 +14,7 @@ func newResolveTestCmd(t *testing.T) *cobra.Command { t.Helper() cmd := &cobra.Command{Use: "test"} cmd.Flags().String("proxy", "", "") - cmd.Flags().StringP("env", "e", "dev", "") + cmd.Flags().StringP("env", "e", "", "") cmd.Flags().String("path", "/", "") cmd.Flags().Bool("allow", false, "") return cmd @@ -60,8 +60,8 @@ func TestResolveEnvironmentName(t *testing.T) { t.Run("flag default is the final fallback", func(t *testing.T) { t.Chdir(t.TempDir()) // no workspace file t.Setenv(INFISICAL_ENVIRONMENT_NAME, "") - if got := ResolveEnvironmentName(newResolveTestCmd(t)); got != "dev" { - t.Fatalf("got %q, want dev", got) + if got := ResolveEnvironmentName(newResolveTestCmd(t)); got != "" { + t.Fatalf("got %q, want empty", got) } }) } From c29d9c4ad5e5b342c1e41e728b40b76d34dfb1b3 Mon Sep 17 00:00:00 2001 From: saif <11242541+saifsmailbox98@users.noreply.github.com> Date: Sun, 19 Jul 2026 05:39:19 +0530 Subject: [PATCH 3/3] fix(agent-proxy): do not source the proxy address from .infisical.json --- packages/cmd/agent_proxy.go | 4 ++-- packages/models/cli.go | 1 - packages/util/helper.go | 11 +++++------ packages/util/resolve_test.go | 14 +++++++------- packages/util/secrets.go | 9 --------- 5 files changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/cmd/agent_proxy.go b/packages/cmd/agent_proxy.go index 2d5323f4..51a7fe43 100644 --- a/packages/cmd/agent_proxy.go +++ b/packages/cmd/agent_proxy.go @@ -109,7 +109,7 @@ func mergeNoProxy(operatorEntries ...string) string { func runAgentProxyConnect(cmd *cobra.Command, args []string) { proxyAddr := util.ResolveAgentProxyAddress(cmd) if proxyAddr == "" { - util.HandleError(fmt.Errorf("the agent proxy address is required; pass --proxy, set INFISICAL_AGENT_PROXY_ADDRESS, or set agentProxyAddress in .infisical.json (e.g. :17322)")) + util.HandleError(fmt.Errorf("the agent proxy address is required; pass --proxy or set INFISICAL_AGENT_PROXY_ADDRESS (e.g. :17322)")) } environment := util.ResolveEnvironmentName(cmd) @@ -429,7 +429,7 @@ func runAgentProcess(args, env []string) error { } func init() { - agentProxyConnectCmd.Flags().String("proxy", "", "address of the agent proxy as host:port (falls back to INFISICAL_AGENT_PROXY_ADDRESS or agentProxyAddress in .infisical.json)") + agentProxyConnectCmd.Flags().String("proxy", "", "address of the agent proxy as host:port (falls back to INFISICAL_AGENT_PROXY_ADDRESS)") agentProxyConnectCmd.Flags().StringP("env", "e", "", "environment slug to fetch proxied services and secrets from (falls back to INFISICAL_ENVIRONMENT or .infisical.json)") agentProxyConnectCmd.Flags().String("path", "/", "secret path (folder) scope (falls back to INFISICAL_SECRET_PATH or defaultSecretPath in .infisical.json)") agentProxyConnectCmd.Flags().String("projectId", "", "project id (falls back to INFISICAL_PROJECT_ID or .infisical.json)") diff --git a/packages/models/cli.go b/packages/models/cli.go index 97c46038..a4ab86ad 100644 --- a/packages/models/cli.go +++ b/packages/models/cli.go @@ -113,7 +113,6 @@ type WorkspaceConfigFile struct { DefaultEnvironment string `json:"defaultEnvironment"` GitBranchToEnvironmentMapping map[string]string `json:"gitBranchToEnvironmentMapping"` DefaultSecretPath string `json:"defaultSecretPath,omitempty"` - AgentProxyAddress string `json:"agentProxyAddress,omitempty"` Domain string `json:"domain,omitempty"` } diff --git a/packages/util/helper.go b/packages/util/helper.go index 5500216c..2c8625bb 100644 --- a/packages/util/helper.go +++ b/packages/util/helper.go @@ -596,17 +596,16 @@ func ResolveSecretPath(cmd *cobra.Command) string { } // ResolveAgentProxyAddress resolves the agent proxy address for `agent-proxy connect`, in order: -// the --proxy flag (if explicitly set) > INFISICAL_AGENT_PROXY_ADDRESS > .infisical.json -// agentProxyAddress > empty (the caller requires a non-empty result). +// the --proxy flag (if explicitly set) > INFISICAL_AGENT_PROXY_ADDRESS > empty (the caller +// requires a non-empty result). It is deliberately NOT sourced from .infisical.json: that file +// is usually committed to a repo, so a poisoned proxy address would silently route all agent +// traffic and its auth token through an attacker-controlled host. func ResolveAgentProxyAddress(cmd *cobra.Command) string { if cmd.Flags().Changed("proxy") { value, _ := cmd.Flags().GetString("proxy") return value } - if value := strings.TrimSpace(os.Getenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME)); value != "" { - return value - } - return GetAgentProxyAddressFromWorkspaceFile() + return strings.TrimSpace(os.Getenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME)) } // GetBoolFlagOrEnv resolves a boolean flag from the flag (if explicitly set), then the given diff --git a/packages/util/resolve_test.go b/packages/util/resolve_test.go index 3af1727d..60c257b5 100644 --- a/packages/util/resolve_test.go +++ b/packages/util/resolve_test.go @@ -103,8 +103,7 @@ func TestResolveSecretPath(t *testing.T) { } func TestResolveAgentProxyAddress(t *testing.T) { - t.Run("flag wins", func(t *testing.T) { - writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) + t.Run("flag wins over env", func(t *testing.T) { t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "env:1") cmd := newResolveTestCmd(t) _ = cmd.Flags().Set("proxy", "flag:1") @@ -113,19 +112,20 @@ func TestResolveAgentProxyAddress(t *testing.T) { } }) - t.Run("env over file", func(t *testing.T) { - writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) + t.Run("env when flag unset", func(t *testing.T) { t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "env:1") if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "env:1" { t.Fatalf("got %q, want env:1", got) } }) - t.Run("file when flag and env unset", func(t *testing.T) { + // The proxy address must never come from .infisical.json (a committed file), or a poisoned + // value could silently redirect all agent traffic. Prove the file is ignored. + t.Run("workspace file is not a source", func(t *testing.T) { writeWorkspace(t, `{"agentProxyAddress":"file:1"}`) t.Setenv(INFISICAL_AGENT_PROXY_ADDRESS_NAME, "") - if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "file:1" { - t.Fatalf("got %q, want file:1", got) + if got := ResolveAgentProxyAddress(newResolveTestCmd(t)); got != "" { + t.Fatalf("got %q, want empty (.infisical.json must not set the proxy address)", got) } }) diff --git a/packages/util/secrets.go b/packages/util/secrets.go index 4dd02938..c1d458ee 100644 --- a/packages/util/secrets.go +++ b/packages/util/secrets.go @@ -511,15 +511,6 @@ func GetSecretPathFromWorkspaceFile() string { return workspaceFile.DefaultSecretPath } -func GetAgentProxyAddressFromWorkspaceFile() string { - workspaceFile, err := GetWorkSpaceFromFile() - if err != nil { - log.Debug().Msgf("GetAgentProxyAddressFromWorkspaceFile: [err=%s]", err) - return "" - } - return workspaceFile.AgentProxyAddress -} - func GetEnvelopmentBasedOnGitBranch(workspaceFile models.WorkspaceConfigFile) string { branch, err := getCurrentBranch() if err != nil {