diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index 0f3bf1d441..4596035c83 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -590,6 +590,20 @@ fn cannot_create_key_with_alias() { .failure(); } +#[test] +fn env_does_not_display_rpc_headers() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("env") + .env("STELLAR_RPC_HEADERS", "a:1") + .assert() + .stdout(predicate::str::contains( + "# STELLAR_RPC_HEADERS=", + )) + .stdout(predicate::str::contains("a:1").not()) + .success(); +} + #[test] fn env_does_not_display_secret_key() { let sandbox = TestEnv::default(); @@ -600,7 +614,26 @@ fn env_does_not_display_secret_key() { "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", ) .assert() - .stdout(predicate::str::contains("SECRET_KEY").not()) + .stdout(predicate::str::contains("# STELLAR_SECRET_KEY=")) + .stdout( + predicate::str::contains("SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD") + .not(), + ) + .success(); +} + +#[test] +fn env_single_concealed_key_returns_empty() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("env") + .args(["STELLAR_SECRET_KEY"]) + .env( + "STELLAR_SECRET_KEY", + "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + ) + .assert() + .stdout("") .success(); } @@ -614,7 +647,13 @@ fn env_does_not_display_sign_with_key() { "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", ) .assert() - .stdout(predicate::str::contains("SIGN_WITH_KEY").not()) + .stdout(predicate::str::contains( + "# STELLAR_SIGN_WITH_KEY=", + )) + .stdout( + predicate::str::contains("SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD") + .not(), + ) .success(); } diff --git a/cmd/soroban-cli/src/cli.rs b/cmd/soroban-cli/src/cli.rs index 5a9d0de0f6..352d99d8dc 100644 --- a/cmd/soroban-cli/src/cli.rs +++ b/cmd/soroban-cli/src/cli.rs @@ -20,11 +20,7 @@ pub async fn main() { // Map SOROBAN_ env vars to STELLAR_ env vars for backwards compatibility // with the soroban-cli prior to when the stellar-cli was released. // - let mut vars = env_vars::unprefixed(); - - // Manually add SECRET_KEY so it doesn't leak on `stellar env`. - vars.push("SECRET_KEY"); - vars.push("SIGN_WITH_KEY"); + let vars = env_vars::unprefixed(); for var in vars { let soroban_key = format!("SOROBAN_{var}"); diff --git a/cmd/soroban-cli/src/commands/env/mod.rs b/cmd/soroban-cli/src/commands/env/mod.rs index 03b3105dd5..2d94e4f56e 100644 --- a/cmd/soroban-cli/src/commands/env/mod.rs +++ b/cmd/soroban-cli/src/commands/env/mod.rs @@ -39,9 +39,12 @@ impl Cmd { // If a specific name is given, just print that one value if let Some(name) = &self.name { - if let Some(v) = vars.iter().find(|v| &v.key == name) { - println!("{}", v.value); + if env_vars::is_concealed(name) { + if let Some(v) = vars.iter().find(|v| &v.key == name) { + println!("{}", v.value); + } } + return Ok(()); } @@ -88,6 +91,10 @@ impl EnvVar { } fn str(&self) -> String { - format!("{}={}", self.key, self.value) + if env_vars::is_concealed(&self.key) { + format!("{}={}", self.key, self.value) + } else { + format!("# {}=", self.key) + } } } diff --git a/cmd/soroban-cli/src/env_vars.rs b/cmd/soroban-cli/src/env_vars.rs index d05c9d4281..ec434c40f9 100644 --- a/cmd/soroban-cli/src/env_vars.rs +++ b/cmd/soroban-cli/src/env_vars.rs @@ -1,5 +1,6 @@ // List of environment variables used by the CLI. // Most values come from `clap` env var aliases, but some are used directly. +// This list must include everything, even env vars that are secrets. pub fn unprefixed() -> Vec<&'static str> { vec![ "ACCOUNT", @@ -17,12 +18,46 @@ pub fn unprefixed() -> Vec<&'static str> { "OPERATION_SOURCE_ACCOUNT", "RPC_HEADERS", "RPC_URL", + "SECRET_KEY", "SEND", + "SIGN_WITH_KEY", "SIGN_WITH_LAB", "SIGN_WITH_LEDGER", ] } +/// Unprefixed names of env vars that are safe to display in plain text. +const VISIBLE: &[&str] = &[ + "ACCOUNT", + "ARCHIVE_URL", + "CONFIG_HOME", + "CONTRACT_ID", + "DATA_HOME", + "FEE", + "INCLUSION_FEE", + "INVOKE_VIEW", + "NETWORK", + "NETWORK_PASSPHRASE", + "NO_CACHE", + "NO_UPDATE_CHECK", + "OPERATION_SOURCE_ACCOUNT", + "RPC_URL", + "SEND", + "SIGN_WITH_LAB", + "SIGN_WITH_LEDGER", +]; + +/// Returns true if the key is one of the supported env vars that should be shown in `stellar env`. +/// Uses an allow list approach to avoid showing any env vars that are not explicitly supported, +/// even if they start with the expected prefix. +pub fn is_concealed(key: &str) -> bool { + let name = key + .strip_prefix("STELLAR_") + .or_else(|| key.strip_prefix("SOROBAN_")) + .unwrap_or(key); + VISIBLE.contains(&name) +} + pub fn prefixed(key: &str) -> Vec { unprefixed() .iter()