From 843abe06213204b611572b9ae809f6826d5bd398 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Mon, 3 Nov 2025 15:10:34 -0700 Subject: [PATCH] feat: Secret injection as environment variables into agent tools --- src/config/agent.rs | 12 +++++++++++- src/config/mod.rs | 24 ++++++++++++++---------- src/vault/mod.rs | 6 ++++-- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/config/agent.rs b/src/config/agent.rs index 4bc0d9c..1c7f206 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -5,7 +5,9 @@ use crate::{ function::{run_llm_function, Functions}, }; +use crate::vault::SECRET_RE; use anyhow::{Context, Result}; +use fancy_regex::Captures; use inquire::{validator::Validation, Text}; use rust_embed::Embed; use serde::{Deserialize, Serialize}; @@ -30,6 +32,7 @@ pub struct Agent { functions: Functions, rag: Option>, model: Model, + vault: GlobalVault, } impl Agent { @@ -195,6 +198,7 @@ impl Agent { functions, rag, model, + vault: Arc::clone(&config.read().vault), }) } @@ -325,7 +329,13 @@ impl Agent { .map(|(k, v)| { ( format!("LLM_AGENT_VAR_{}", normalize_env_name(k)), - v.clone(), + SECRET_RE + .replace(v, |caps: &Captures| { + self.vault + .get_secret(caps[1].trim(), false) + .unwrap_or(v.clone()) + }) + .to_string(), ) }) .collect() diff --git a/src/config/mod.rs b/src/config/mod.rs index c61a6ba..12d400f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -24,7 +24,7 @@ use crate::utils::*; use crate::mcp::{ McpRegistry, MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX, }; -use crate::vault::{create_vault_password_file, interpolate_secrets, Vault}; +use crate::vault::{create_vault_password_file, interpolate_secrets, GlobalVault, Vault}; use anyhow::{anyhow, bail, Context, Result}; use fancy_regex::Regex; use indexmap::IndexMap; @@ -159,7 +159,6 @@ pub struct Config { pub left_prompt: Option, pub right_prompt: Option, - pub serve_addr: Option, pub user_agent: Option, pub save_shell_history: bool, pub sync_models_url: Option, @@ -167,7 +166,7 @@ pub struct Config { pub clients: Vec, #[serde(skip)] - pub vault: Vault, + pub vault: GlobalVault, #[serde(skip)] pub macro_flag: bool, @@ -244,7 +243,6 @@ impl Default for Config { left_prompt: None, right_prompt: None, - serve_addr: None, user_agent: None, save_shell_history: true, sync_models_url: None, @@ -317,7 +315,9 @@ impl Config { let (parsed_config, missing_secrets) = interpolate_secrets(&content, &vault); if !missing_secrets.is_empty() && !info_flag { - debug!("Global config references secrets that are missing from the vault: {missing_secrets:?}"); + debug!( + "Global config references secrets that are missing from the vault: {missing_secrets:?}" + ); return Err(anyhow!(formatdoc!( " Global config file references secrets that are missing from the vault: {:?} @@ -341,7 +341,7 @@ impl Config { config.working_mode = working_mode; config.info_flag = info_flag; - config.vault = vault; + config.vault = Arc::new(vault); Agent::install_builtin_agents()?; @@ -769,7 +769,9 @@ impl Config { if let Some(servers) = value.as_ref() { if let Some(registry) = &config.read().mcp_registry { if registry.list_configured_servers().is_empty() { - bail!("No MCP servers are configured. Please configure MCP servers first before setting 'use_mcp_servers'."); + bail!( + "No MCP servers are configured. Please configure MCP servers first before setting 'use_mcp_servers'." + ); } if !servers.split(',').all(|s| { @@ -778,7 +780,9 @@ impl Config { .contains(&s.trim().to_string()) || s == "all" }) { - bail!("Some of the specified MCP servers in 'use_mcp_servers' are configured. Please check your MCP server configuration."); + bail!( + "Some of the specified MCP servers in 'use_mcp_servers' are configured. Please check your MCP server configuration." + ); } } } @@ -2562,8 +2566,8 @@ impl Config { None => String::new(), }; let output = format!( - "# CHAT: {summary} [{now}]{scope}\n{raw_input}\n--------\n{tool_calls}{output}\n--------\n\n", - ); + "# CHAT: {summary} [{now}]{scope}\n{raw_input}\n--------\n{tool_calls}{output}\n--------\n\n", + ); file.write_all(output.as_bytes()) .with_context(|| "Failed to save message") } diff --git a/src/vault/mod.rs b/src/vault/mod.rs index df4e9ad..b12f1d2 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -12,16 +12,18 @@ use fancy_regex::Regex; use gman::providers::local::LocalProvider; use gman::providers::SecretProvider; use inquire::{required, Password, PasswordDisplayMode}; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use tokio::runtime::Handle; -static SECRET_RE: LazyLock = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap()); +pub static SECRET_RE: LazyLock = LazyLock::new(|| Regex::new(r"\{\{(.+)}}").unwrap()); #[derive(Debug, Default, Clone)] pub struct Vault { local_provider: LocalProvider, } +pub type GlobalVault = Arc; + impl Vault { pub fn init_bare() -> Self { let vault_password_file = Config::default().vault_password_file();