Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 74 additions & 61 deletions crates/goose/src/agents/extension_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,6 @@ impl ExtensionManager {
return Ok(());
}

// Resolve working_dir: explicit > current_dir
let effective_working_dir =
working_dir.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

let mut temp_dir = None;

let client: Box<dyn McpClientTrait> = match &config {
Expand Down Expand Up @@ -577,63 +573,6 @@ impl ExtensionManager {
)
.await?
}
ExtensionConfig::Stdio {
cmd,
args,
envs,
env_keys,
timeout,
..
} => {
let config = Config::global();
let mut all_envs =
merge_environments(envs, env_keys, &sanitized_name, config).await?;

if let Some(sid) = session_id {
all_envs.insert("AGENT_SESSION_ID".to_string(), sid.to_string());
}

// Check for malicious packages before launching the process
extension_malware_check::deny_if_malicious_cmd_args(cmd, args).await?;

let command = if let Some(container) = container {
let container_id = container.id();
tracing::info!(
container = %container_id,
cmd = %cmd,
"Starting stdio extension inside Docker container"
);
Command::new("docker").configure(|command| {
command.arg("exec").arg("-i");
for (key, value) in &all_envs {
command.arg("-e").arg(format!("{}={}", key, value));
}
command.arg(container_id);
command.arg(cmd);
command.args(args);
})
} else {
let cmd = resolve_command(cmd);
Command::new(cmd).configure(|command| {
command.args(args).envs(all_envs);
})
};

let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
let client = child_process_client(
command,
timeout,
self.provider.clone(),
Some(&effective_working_dir),
container.map(|c| c.id().to_string()),
self.client_name.clone(),
capabilities,
)
.await?;
Box::new(client)
}
ExtensionConfig::Builtin { name, timeout, .. } => {
let timeout_duration = Duration::from_secs(timeout.unwrap_or(300));
let normalized_name = name_to_key(name);
Expand All @@ -660,9 +599,14 @@ impl ExtensionManager {
.arg(&normalized_name);
});

let effective_working_dir = working_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};

let client = child_process_client(
command,
timeout,
Expand All @@ -675,12 +619,16 @@ impl ExtensionManager {
.await?;
Box::new(client)
} else {
// Non-containerized builtin runs in-process via duplex channels.
// Working directory is passed per-request via call_tool metadata, not here.
let (server_read, client_write) = tokio::io::duplex(65536);
let (client_read, server_write) = tokio::io::duplex(65536);
extension_fn(server_read, server_write);

let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};

Box::new(
McpClient::connect(
(client_read, client_write),
Expand All @@ -693,6 +641,66 @@ impl ExtensionManager {
)
}
}
ExtensionConfig::Stdio {
cmd,
args,
envs,
env_keys,
timeout,
..
} => {
let config = Config::global();
let mut all_envs =
merge_environments(envs, env_keys, &sanitized_name, config).await?;

if let Some(sid) = session_id {
all_envs.insert("AGENT_SESSION_ID".to_string(), sid.to_string());
}

// Check for malicious packages before launching the process
extension_malware_check::deny_if_malicious_cmd_args(cmd, args).await?;

let command = if let Some(container) = container {
let container_id = container.id();
tracing::info!(
container = %container_id,
cmd = %cmd,
"Starting stdio extension inside Docker container"
);
Command::new("docker").configure(|command| {
command.arg("exec").arg("-i");
for (key, value) in &all_envs {
command.arg("-e").arg(format!("{}={}", key, value));
}
command.arg(container_id);
command.arg(cmd);
command.args(args);
})
} else {
let cmd = resolve_command(cmd);
Command::new(cmd).configure(|command| {
command.args(args).envs(all_envs);
})
};

let effective_working_dir = working_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
let client = child_process_client(
command,
timeout,
self.provider.clone(),
Some(&effective_working_dir),
container.map(|c| c.id().to_string()),
self.client_name.clone(),
capabilities,
)
.await?;
Box::new(client)
}
ExtensionConfig::Platform { name, .. } => {
let normalized_key = name_to_key(name);
let def = PLATFORM_EXTENSIONS
Expand Down Expand Up @@ -724,6 +732,11 @@ impl ExtensionManager {
command.arg("python").arg(file_path.to_str().unwrap());
});

// Compute working_dir for InlinePython (runs as child process via uvx)
let effective_working_dir = working_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ export default function ProviderConfigurationModal({
Object.entries(configValues)
.filter(
([_k, entry]) =>
!!entry.value ||
(entry.serverValue != null && typeof entry.serverValue === 'string')
!!entry.value || (entry.serverValue != null && typeof entry.serverValue === 'string')
)
.map(([k, entry]) => [
k,
Expand Down
9 changes: 1 addition & 8 deletions ui/desktop/src/sandbox/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,7 @@ describe('checkBlocked', () => {
});

it('does not block loopback by default, blocks when opted in', async () => {
const allowed = await checkBlocked(
'localhost',
8080,
blocked,
noLD,
noLDCache,
defaultOptions
);
const allowed = await checkBlocked('localhost', 8080, blocked, noLD, noLDCache, defaultOptions);
expect(allowed.blocked).toBe(false);

const blocked_ = await checkBlocked('localhost', 8080, blocked, noLD, noLDCache, {
Expand Down
Loading