diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 2894cec629..9f567c7b9b 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1179,7 +1179,9 @@ Prints the environment variables Prints to stdout in a format that can be used as .env file. Environment variables have precedence over defaults. -Pass a name to get the value of a single environment variable. +By default, secret values are concealed. To display them, use `--reveal`. + +Pass a name to get the value of a single environment variable. Its value is printed without shell quoting (control characters are neutralized), suitable for command substitution. Concealed variables print nothing unless `--reveal` is passed. If there are no environment variables in use, prints the defaults. @@ -1195,6 +1197,10 @@ If there are no environment variables in use, prints the defaults. - `--config-dir ` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings +###### **Options:** + +- `--reveal` — Whether to reveal the value of concealed env vars. By default, concealed env vars are hidden behind a placeholder value + ## `stellar keys` Create and manage identities including keys and addresses diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index cf14856d52..ab8233b373 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -775,6 +775,23 @@ fn env_does_not_display_secret_key() { .success(); } +#[test] +fn env_does_display_secret_key_when_reveal_is_provided() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("env") + .arg("--reveal") + .env( + "STELLAR_SECRET_KEY", + "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + ) + .assert() + .stdout(predicate::str::contains( + "STELLAR_SECRET_KEY=SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + )) + .success(); +} + #[test] fn env_single_concealed_key_returns_empty() { let sandbox = TestEnv::default(); @@ -790,6 +807,21 @@ fn env_single_concealed_key_returns_empty() { .success(); } +#[test] +fn env_single_is_returned_when_using_reveal() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("env") + .args(["STELLAR_SECRET_KEY", "--reveal"]) + .env( + "STELLAR_SECRET_KEY", + "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + ) + .assert() + .stdout("SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD\n") + .success(); +} + #[test] fn env_does_not_display_sign_with_key() { let sandbox = TestEnv::default(); diff --git a/cmd/soroban-cli/src/commands/env/mod.rs b/cmd/soroban-cli/src/commands/env/mod.rs index 2d94e4f56e..4f1ec1676e 100644 --- a/cmd/soroban-cli/src/commands/env/mod.rs +++ b/cmd/soroban-cli/src/commands/env/mod.rs @@ -3,8 +3,10 @@ use crate::{ config::locator::{self}, env_vars, print::Print, + utils::escape_control_characters, }; use clap::Parser; +use shell_escape::escape; #[allow(clippy::doc_markdown)] #[derive(Debug, Parser)] @@ -15,6 +17,11 @@ pub struct Cmd { #[arg()] pub name: Option, + /// Whether to reveal the value of concealed env vars. By default, concealed env vars are + /// hidden behind a placeholder value. + #[arg(long)] + pub reveal: bool, + #[command(flatten)] pub config_locator: locator::Args, } @@ -32,16 +39,20 @@ impl Cmd { let supported = env_vars::prefixed("STELLAR"); for key in supported { - if let Some(v) = EnvVar::get(&key) { + if let Some(mut v) = EnvVar::get(&key) { + if self.reveal { + v.reveal(); + } + vars.push(v); } } // If a specific name is given, just print that one value if let Some(name) = &self.name { - if env_vars::is_concealed(name) { - if let Some(v) = vars.iter().find(|v| &v.key == name) { - println!("{}", v.value); + if let Some(v) = vars.iter().find(|v| &v.key == name) { + if v.is_revealed() { + println!("{}", escape_control_characters(&v.value)); } } @@ -70,6 +81,7 @@ struct EnvVar { key: String, value: String, source: String, + reveal: bool, } impl EnvVar { @@ -84,15 +96,25 @@ impl EnvVar { key: key.to_string(), value, source, + reveal: false, }); } None } + fn reveal(&mut self) { + self.reveal = true; + } + + fn is_revealed(&self) -> bool { + self.reveal || !env_vars::is_concealed(&self.key) + } + fn str(&self) -> String { - if env_vars::is_concealed(&self.key) { - format!("{}={}", self.key, self.value) + if self.is_revealed() { + let value = escape(escape_control_characters(&self.value).into()); + format!("{}={value}", self.key) } else { format!("# {}=", self.key) } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 4da22942ac..90e5d9683f 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -157,7 +157,11 @@ pub enum Cmd { /// Prints to stdout in a format that can be used as .env file. Environment /// variables have precedence over defaults. /// - /// Pass a name to get the value of a single environment variable. + /// By default, secret values are concealed. To display them, use `--reveal`. + /// + /// Pass a name to get the value of a single environment variable. Its value is printed without + /// shell quoting (control characters are neutralized), suitable for command substitution. + /// Concealed variables print nothing unless `--reveal` is passed. /// /// If there are no environment variables in use, prints the defaults. Env(env::Cmd), diff --git a/cmd/soroban-cli/src/env_vars.rs b/cmd/soroban-cli/src/env_vars.rs index ec434c40f9..faf732438c 100644 --- a/cmd/soroban-cli/src/env_vars.rs +++ b/cmd/soroban-cli/src/env_vars.rs @@ -29,7 +29,6 @@ pub fn unprefixed() -> Vec<&'static str> { /// 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", @@ -41,21 +40,20 @@ const VISIBLE: &[&str] = &[ "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. +/// Returns true if the key should be concealed in `stellar env` output, i.e. it is not in the +/// allow list of vars that are safe to display. Using an allow list ensures unknown vars are +/// concealed by default, 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) + !VISIBLE.contains(&name) } pub fn prefixed(key: &str) -> Vec {