mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
fix: resolve telegram/slack name collision between tool and channel registries (#346)
When installing the Telegram WASM channel via the web UI, a name collision between registry/tools/telegram.json and registry/channels/telegram.json caused the tool entry to win, installing to ~/.ironclaw/tools/ instead of ~/.ironclaw/channels/. This made activation fail with "WASM runtime not available". - Add `get_with_kind()` to ExtensionRegistry for kind-aware lookup - Use `kind_hint` parameter in `install()` to resolve collisions - Rename tool entries to avoid future collisions: telegram → telegram-mtproto, slack → slack-tool - Fix `_bundles.json` stale reference (tools/slack → tools/slack-tool) - Fix `cache_discovered()` to deduplicate by (name, kind) consistently - Add path traversal validation to install/activate/remove entry points - Add tests for kind-aware lookup, discovery cache, and bundle resolution Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b0b3a50fa3
commit
e9f32eaebe
+16
-10
@@ -191,9 +191,10 @@ impl ExtensionManager {
|
||||
kind_hint: Option<ExtensionKind>,
|
||||
) -> Result<InstallResult, ExtensionError> {
|
||||
tracing::info!(extension = %name, url = ?url, kind = ?kind_hint, "Installing extension");
|
||||
Self::validate_extension_name(name)?;
|
||||
|
||||
// If we have a registry entry, use it
|
||||
if let Some(entry) = self.registry.get(name).await {
|
||||
// If we have a registry entry, use it (prefer kind_hint to resolve collisions)
|
||||
if let Some(entry) = self.registry.get_with_kind(name, kind_hint).await {
|
||||
return self.install_from_entry(&entry).await.map_err(|e| {
|
||||
tracing::error!(extension = %name, error = %e, "Extension install failed");
|
||||
e
|
||||
@@ -245,6 +246,7 @@ impl ExtensionManager {
|
||||
|
||||
/// Activate an installed (and optionally authenticated) extension.
|
||||
pub async fn activate(&self, name: &str) -> Result<ActivateResult, ExtensionError> {
|
||||
Self::validate_extension_name(name)?;
|
||||
let kind = self.determine_installed_kind(name).await?;
|
||||
|
||||
match kind {
|
||||
@@ -399,6 +401,7 @@ impl ExtensionManager {
|
||||
|
||||
/// Remove an installed extension.
|
||||
pub async fn remove(&self, name: &str) -> Result<String, ExtensionError> {
|
||||
Self::validate_extension_name(name)?;
|
||||
let kind = self.determine_installed_kind(name).await?;
|
||||
|
||||
match kind {
|
||||
@@ -1732,14 +1735,6 @@ impl ExtensionManager {
|
||||
)));
|
||||
}
|
||||
|
||||
// Validate name to prevent path traversal
|
||||
if name.contains('/') || name.contains('\\') || name.contains("..") || name.contains('\0') {
|
||||
return Err(ExtensionError::ActivationFailed(format!(
|
||||
"Invalid channel name '{}': contains path separator or traversal characters",
|
||||
name
|
||||
)));
|
||||
}
|
||||
|
||||
// Load the channel from files
|
||||
let wasm_path = self.wasm_channels_dir.join(format!("{}.wasm", name));
|
||||
let cap_path = self
|
||||
@@ -2033,6 +2028,17 @@ impl ExtensionManager {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Reject names containing path separators or traversal sequences.
|
||||
fn validate_extension_name(name: &str) -> Result<(), ExtensionError> {
|
||||
if name.contains('/') || name.contains('\\') || name.contains("..") || name.contains('\0') {
|
||||
return Err(ExtensionError::InstallFailed(format!(
|
||||
"Invalid extension name '{}': contains path separator or traversal characters",
|
||||
name
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cleanup_expired_auths(&self) {
|
||||
let mut pending = self.pending_auth.write().await;
|
||||
pending.retain(|_, auth| auth.created_at.elapsed() < std::time::Duration::from_secs(300));
|
||||
|
||||
Reference in New Issue
Block a user