From dffaf6b9db6f113adea4b3c656b84b0fdce17cd9 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 18 Aug 2026 15:10:30 -0600 Subject: [PATCH] fix: drain tty input when displaying inquire prompts to prevent unintentional escapes --- src/utils/mod.rs | 30 +++++++++++++++++++++++++++++- src/vault/mod.rs | 3 +++ src/vault/utils.rs | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 428e882..4a5be52 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -35,6 +35,7 @@ use nu_ansi_term::Color; use serde_json::Value; use std::borrow::Cow; use std::collections::VecDeque; +use std::io; use std::sync::atomic::AtomicBool; use std::sync::{LazyLock, Mutex, OnceLock}; use std::{cmp, env, path::PathBuf, process}; @@ -45,7 +46,7 @@ pub static CODE_BLOCK_RE: LazyLock = LazyLock::new(|| Regex::new(r"(?ms)```\w*(.*)```").unwrap()); pub static THINK_TAG_RE: LazyLock = LazyLock::new(|| Regex::new(r"(?s)^\s*.*?(\s*|$)").unwrap()); -pub static IS_STDOUT_TERMINAL: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); +pub static IS_STDOUT_TERMINAL: LazyLock = LazyLock::new(|| io::stdout().is_terminal()); pub static HEADLESS: AtomicBool = AtomicBool::new(false); pub static ACP_SERVER: AtomicBool = AtomicBool::new(false); @@ -133,6 +134,33 @@ pub fn parse_bool(value: &str) -> Option { } } +pub fn drain_stale_tty_input() { + use crossterm::event::{poll, read}; + use std::time::{Duration, Instant}; + + if !io::stdin().is_terminal() { + return; + } + + if crossterm::terminal::enable_raw_mode().is_err() { + return; + } + + let deadline = Instant::now() + Duration::from_millis(100); + while Instant::now() < deadline { + match poll(Duration::from_millis(10)) { + Ok(true) => { + if read().is_err() { + break; + } + } + _ => break, + } + } + + let _ = crossterm::terminal::disable_raw_mode(); +} + pub fn estimate_token_length(text: &str) -> usize { let weighted: usize = text.chars().map(|c| if c.is_ascii() { 1 } else { 2 }).sum(); weighted.div_ceil(4) diff --git a/src/vault/mod.rs b/src/vault/mod.rs index a16a208..2573520 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -12,6 +12,7 @@ pub use utils::prompt_provider_choice; use crate::cli::Cli; use crate::config::AppConfig; +use crate::utils::drain_stale_tty_input; use crate::vault::utils::ensure_password_file_initialized; use anyhow::{Context, Result, anyhow, bail}; use fancy_regex::Regex; @@ -151,6 +152,7 @@ impl Vault { "Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host." ); } + drain_stale_tty_input(); let secret_value = Password::new("Enter the secret value:") .with_validator(required!()) .with_display_mode(PasswordDisplayMode::Masked) @@ -190,6 +192,7 @@ impl Vault { "Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host." ); } + drain_stale_tty_input(); let secret_value = Password::new("Enter the secret value:") .with_validator(required!()) .with_display_mode(PasswordDisplayMode::Masked) diff --git a/src/vault/utils.rs b/src/vault/utils.rs index 64cdaf6..ecd7454 100644 --- a/src/vault/utils.rs +++ b/src/vault/utils.rs @@ -1,5 +1,6 @@ use crate::config::ensure_parent_exists; use crate::sandbox::{SANDBOX_ENV_FLAG, sandbox_secret_env_var}; +use crate::utils::drain_stale_tty_input; use crate::vault::{SECRET_RE, Vault}; use anyhow::Result; use anyhow::anyhow; @@ -68,6 +69,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { } } + drain_stale_tty_input(); let ans = Confirm::new( format!( "The configured password file '{}' is empty. Create a password?", @@ -107,6 +109,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { } } } else { + drain_stale_tty_input(); let ans = Confirm::new("No password file configured. Do you want to create one now?") .with_default(true) .prompt()?; @@ -185,6 +188,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { } pub fn prompt_provider_choice() -> Result> { + drain_stale_tty_input(); let choices = vec![ "local - encrypted file on this machine", "aws_secrets_manager - AWS Secrets Manager",