From 6adf95b6d14a1699fb85db3564d0b01f52af359c Mon Sep 17 00:00:00 2001 From: Henry Park Date: Mon, 2 Mar 2026 16:56:24 -0800 Subject: [PATCH] fix: wire secrets store into all WASM runtime activation paths (#479) WASM tools and channels activated at runtime (via web UI or CLI) were missing secrets store wiring, causing credential injection to silently fail. Tools like web-search would get 401s from APIs even though the user had configured their API key. Four bugs fixed: - activate_wasm_tool(): WasmToolLoader created without .with_secrets_store() - register_wasm_from_storage(): hardcoded secrets_store: None - WasmChannelLoader: no secrets_store field at all (added field + builder) - activate_wasm_channel() and startup path: both missed wiring secrets The startup path in app.rs was correct; all runtime paths now match it. Co-authored-by: Claude Sonnet 4.6 --- src/channels/wasm/loader.rs | 14 +++++++++++++- src/extensions/manager.rs | 6 ++++-- src/main.rs | 5 ++++- src/tools/registry.rs | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/channels/wasm/loader.rs b/src/channels/wasm/loader.rs index 57372c2e..728a2bde 100644 --- a/src/channels/wasm/loader.rs +++ b/src/channels/wasm/loader.rs @@ -19,12 +19,14 @@ use crate::channels::wasm::schema::ChannelCapabilitiesFile; use crate::channels::wasm::wrapper::WasmChannel; use crate::db::SettingsStore; use crate::pairing::PairingStore; +use crate::secrets::SecretsStore; /// Loads WASM channels from the filesystem. pub struct WasmChannelLoader { runtime: Arc, pairing_store: Arc, settings_store: Option>, + secrets_store: Option>, } impl WasmChannelLoader { @@ -38,9 +40,16 @@ impl WasmChannelLoader { runtime, pairing_store, settings_store, + secrets_store: None, } } + /// Set the secrets store for host-based credential injection in WASM channels. + pub fn with_secrets_store(mut self, store: Arc) -> Self { + self.secrets_store = Some(store); + self + } + /// Load a single WASM channel from a file pair. /// /// Expects: @@ -127,7 +136,7 @@ impl WasmChannelLoader { .await?; // Create the channel - let channel = WasmChannel::new( + let mut channel = WasmChannel::new( self.runtime.clone(), prepared, capabilities, @@ -135,6 +144,9 @@ impl WasmChannelLoader { self.pairing_store.clone(), self.settings_store.clone(), ); + if let Some(ref secrets) = self.secrets_store { + channel = channel.with_secrets_store(Arc::clone(secrets)); + } tracing::info!( name = name, diff --git a/src/extensions/manager.rs b/src/extensions/manager.rs index ee79c1d0..9c29d5e8 100644 --- a/src/extensions/manager.rs +++ b/src/extensions/manager.rs @@ -1804,7 +1804,8 @@ impl ExtensionManager { None }; - let loader = WasmToolLoader::new(Arc::clone(runtime), Arc::clone(&self.tool_registry)); + let loader = WasmToolLoader::new(Arc::clone(runtime), Arc::clone(&self.tool_registry)) + .with_secrets_store(Arc::clone(&self.secrets)); loader .load_from_files(name, &wasm_path, cap_path_option) .await @@ -1915,7 +1916,8 @@ impl ExtensionManager { Arc::clone(&channel_runtime), Arc::clone(&pairing_store), settings_store, - ); + ) + .with_secrets_store(Arc::clone(&self.secrets)); let loaded = loader .load_from_files(name, &wasm_path, cap_path_option) .await diff --git a/src/main.rs b/src/main.rs index 08728307..71118e35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -911,11 +911,14 @@ async fn setup_wasm_channels( let pairing_store = Arc::new(PairingStore::new()); let settings_store: Option> = database.map(|db| Arc::clone(db) as Arc); - let loader = WasmChannelLoader::new( + let mut loader = WasmChannelLoader::new( Arc::clone(&runtime), Arc::clone(&pairing_store), settings_store, ); + if let Some(secrets) = secrets_store { + loader = loader.with_secrets_store(Arc::clone(secrets)); + } let results = match loader .load_from_dir(&config.channels.wasm_channels_dir) diff --git a/src/tools/registry.rs b/src/tools/registry.rs index 21d370b9..5b72a3e9 100644 --- a/src/tools/registry.rs +++ b/src/tools/registry.rs @@ -585,7 +585,7 @@ impl ToolRegistry { limits: None, description: Some(&tool_with_binary.tool.description), schema: Some(tool_with_binary.tool.parameters_schema.clone()), - secrets_store: None, + secrets_store: self.secrets_store.clone(), oauth_refresh: None, }) .await