diff --git a/Cargo.lock b/Cargo.lock index 73733e1..086e034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,7 @@ dependencies = [ "tempfile", "thiserror", "tracing", + "zeroize", ] [[package]] @@ -845,6 +846,7 @@ dependencies = [ "clap", "dirs", "enclaveapp-app-adapter", + "enclaveapp-core", "fs4", "serde", "serde_json", diff --git a/npmenc-core/Cargo.toml b/npmenc-core/Cargo.toml index b14a5db..61c33ce 100644 --- a/npmenc-core/Cargo.toml +++ b/npmenc-core/Cargo.toml @@ -11,6 +11,7 @@ anyhow = { workspace = true } clap = { workspace = true } dirs = { workspace = true } enclaveapp-app-adapter = { workspace = true } +enclaveapp-core = { workspace = true } fs4 = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/npmenc-core/src/token_source.rs b/npmenc-core/src/token_source.rs index f67d234..894b500 100644 --- a/npmenc-core/src/token_source.rs +++ b/npmenc-core/src/token_source.rs @@ -1374,12 +1374,31 @@ fn acquire_secret_from_command_with_env( command: TokenSourceCommand, env_overrides: &[(String, String)], ) -> Result { + use enclaveapp_core::timeout::{run_with_timeout, TimeoutResult}; + use std::time::Duration; + + // Credential helpers (1Password CLI, AWS Secrets Manager, custom scripts) + // are normally fast but can stall on network issues. Cap at 60s by + // default; override via NPMENC_TOKEN_SOURCE_TIMEOUT_SECS. + let timeout_secs = std::env::var("NPMENC_TOKEN_SOURCE_TIMEOUT_SECS") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or(60); + let mut process = Command::new(&command.program); process.args(&command.args); for (key, value) in env_overrides { process.env(key, value); } - let output = process.output()?; + let output = match run_with_timeout(process, Duration::from_secs(timeout_secs))? { + TimeoutResult::Completed(o) => o, + TimeoutResult::TimedOut => { + return Err(anyhow!( + "token source `{}` did not respond within {timeout_secs}s (set NPMENC_TOKEN_SOURCE_TIMEOUT_SECS to override)", + serialize_token_source(&command) + )); + } + }; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); if stderr.is_empty() {