mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(cli): mask credential env values in --help output (#3570)
clap renders live env var values in help text by default. Three args carrying credentials were exposed this way: - `BUZZ_PRIVATE_KEY` in `buzz-cli` (`crates/buzz-cli/src/lib.rs`) - `BUZZ_AUTH_TAG` in `buzz-cli` - `BUZZ_PRIVATE_KEY` in `buzz-acp` (`crates/buzz-acp/src/config.rs`) Add `hide_env_values = true` to each. Env var names remain visible for discoverability; only their runtime values are withheld from `--help` output. Also adds a regression guard in each crate's test module that walks the clap command tree (recursing into subcommands for `buzz-cli`) and asserts every arg whose env var name contains `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `CRED`, or `AUTH` has `hide_env_values` set. This prevents future credential-bearing args from being added without the masking in place. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -82,11 +82,11 @@ struct Cli {
|
||||
relay: String,
|
||||
|
||||
/// Nostr private key (hex or nsec). This is the CLI's identity.
|
||||
#[arg(long, env = "BUZZ_PRIVATE_KEY")]
|
||||
#[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)]
|
||||
private_key: Option<String>,
|
||||
|
||||
/// NIP-OA auth tag JSON (owner attestation). Injected into every signed event.
|
||||
#[arg(long, env = "BUZZ_AUTH_TAG")]
|
||||
#[arg(long, env = "BUZZ_AUTH_TAG", hide_env_values = true)]
|
||||
auth_tag: Option<String>,
|
||||
|
||||
/// Output format: 'json' (default, full fields) or 'compact' (reduced fields).
|
||||
@@ -2075,4 +2075,46 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all args (recursing into subcommands) whose env var name looks
|
||||
/// like a credential but does NOT have `hide_env_values` set.
|
||||
fn collect_unhidden_secret_args(cmd: &clap::Command) -> Vec<(String, String)> {
|
||||
const SECRET_PATTERNS: &[&str] = &["KEY", "SECRET", "TOKEN", "PASSWORD", "CRED", "AUTH"];
|
||||
|
||||
let mut violations: Vec<(String, String)> = Vec::new();
|
||||
|
||||
for arg in cmd.get_arguments() {
|
||||
if let Some(env_key) = arg.get_env() {
|
||||
let env_name = env_key.to_string_lossy().to_uppercase();
|
||||
let is_secret = SECRET_PATTERNS.iter().any(|pat| env_name.contains(pat));
|
||||
if is_secret && !arg.is_hide_env_values_set() {
|
||||
violations.push((cmd.get_name().to_string(), env_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for sub in cmd.get_subcommands() {
|
||||
violations.extend(collect_unhidden_secret_args(sub));
|
||||
}
|
||||
|
||||
violations
|
||||
}
|
||||
|
||||
/// Every arg whose env var name contains KEY/SECRET/TOKEN/PASSWORD/CRED/AUTH
|
||||
/// must set `hide_env_values = true` to prevent credential leakage in --help.
|
||||
#[test]
|
||||
fn secret_env_args_hide_their_values_in_help() {
|
||||
let cmd = Cli::command();
|
||||
let violations = collect_unhidden_secret_args(&cmd);
|
||||
assert!(
|
||||
violations.is_empty(),
|
||||
"Found secret-bearing env args without hide_env_values=true. \
|
||||
Add `hide_env_values = true` to each:\n{}",
|
||||
violations
|
||||
.iter()
|
||||
.map(|(cmd, env)| format!(" command={cmd:?} env={env:?}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user