diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index ff8cf8db969e9ef2d1d86b306c0f38fb66a67fde..534f1d47868d6c530468a9095427197d74ddf9dc 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -7,7 +7,6 @@ use db::kvp::{Dismissable, KEY_VALUE_STORE}; use project::{ ExternalAgentServerName, agent_server_store::{CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME}, - trusted_worktrees::{RemoteHostLocation, TrustedWorktrees, wait_for_workspace_trust}, }; use serde::{Deserialize, Serialize}; use settings::{ @@ -264,17 +263,6 @@ impl AgentType { Self::Custom { .. } => Some(IconName::Sparkle), } } - - fn is_mcp(&self) -> bool { - match self { - Self::NativeAgent => false, - Self::TextThread => false, - Self::Custom { .. } => false, - Self::Gemini => true, - Self::ClaudeCode => true, - Self::Codex => true, - } - } } impl From for AgentType { @@ -455,9 +443,7 @@ pub struct AgentPanel { pending_serialization: Option>>, onboarding: Entity, selected_agent: AgentType, - new_agent_thread_task: Task<()>, show_trust_workspace_message: bool, - _worktree_trust_subscription: Option, } impl AgentPanel { @@ -681,48 +667,6 @@ impl AgentPanel { None }; - let mut show_trust_workspace_message = false; - let worktree_trust_subscription = - TrustedWorktrees::try_get_global(cx).and_then(|trusted_worktrees| { - let has_global_trust = trusted_worktrees.update(cx, |trusted_worktrees, cx| { - trusted_worktrees.can_trust_workspace( - project - .read(cx) - .remote_connection_options(cx) - .map(RemoteHostLocation::from), - cx, - ) - }); - if has_global_trust { - None - } else { - show_trust_workspace_message = true; - let project = project.clone(); - Some(cx.subscribe( - &trusted_worktrees, - move |agent_panel, trusted_worktrees, _, cx| { - let new_show_trust_workspace_message = - !trusted_worktrees.update(cx, |trusted_worktrees, cx| { - trusted_worktrees.can_trust_workspace( - project - .read(cx) - .remote_connection_options(cx) - .map(RemoteHostLocation::from), - cx, - ) - }); - if new_show_trust_workspace_message - != agent_panel.show_trust_workspace_message - { - agent_panel.show_trust_workspace_message = - new_show_trust_workspace_message; - cx.notify(); - }; - }, - )) - } - }); - let mut panel = Self { active_view, workspace, @@ -745,14 +689,12 @@ impl AgentPanel { height: None, zoomed: false, pending_serialization: None, - new_agent_thread_task: Task::ready(()), onboarding, acp_history, history_store, selected_agent: AgentType::default(), loading: false, - show_trust_workspace_message, - _worktree_trust_subscription: worktree_trust_subscription, + show_trust_workspace_message: false, }; // Initial sync of agent servers from extensions @@ -945,47 +887,6 @@ impl AgentPanel { } }; - if ext_agent.is_mcp() { - let wait_task = this.update(cx, |agent_panel, cx| { - agent_panel.project.update(cx, |project, cx| { - wait_for_workspace_trust( - project.remote_connection_options(cx), - "context servers", - cx, - ) - }) - })?; - if let Some(wait_task) = wait_task { - this.update_in(cx, |agent_panel, window, cx| { - agent_panel.show_trust_workspace_message = true; - cx.notify(); - agent_panel.new_agent_thread_task = - cx.spawn_in(window, async move |agent_panel, cx| { - wait_task.await; - let server = ext_agent.server(fs, history); - agent_panel - .update_in(cx, |agent_panel, window, cx| { - agent_panel.show_trust_workspace_message = false; - cx.notify(); - agent_panel._external_thread( - server, - resume_thread, - summarize_thread, - workspace, - project, - loading, - ext_agent, - window, - cx, - ); - }) - .ok(); - }); - })?; - return Ok(()); - } - } - let server = ext_agent.server(fs, history); this.update_in(cx, |agent_panel, window, cx| { agent_panel._external_thread( @@ -1510,36 +1411,6 @@ impl AgentPanel { window: &mut Window, cx: &mut Context, ) { - let wait_task = if agent.is_mcp() { - self.project.update(cx, |project, cx| { - wait_for_workspace_trust( - project.remote_connection_options(cx), - "context servers", - cx, - ) - }) - } else { - None - }; - if let Some(wait_task) = wait_task { - self.show_trust_workspace_message = true; - cx.notify(); - self.new_agent_thread_task = cx.spawn_in(window, async move |agent_panel, cx| { - wait_task.await; - agent_panel - .update_in(cx, |agent_panel, window, cx| { - agent_panel.show_trust_workspace_message = false; - cx.notify(); - agent_panel._new_agent_thread(agent, window, cx); - }) - .ok(); - }); - } else { - self._new_agent_thread(agent, window, cx); - } - } - - fn _new_agent_thread(&mut self, agent: AgentType, window: &mut Window, cx: &mut Context) { match agent { AgentType::TextThread => { window.dispatch_action(NewTextThread.boxed_clone(), cx); diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 4f759d6a9c7687d2cdf29752c489db2fcb1ffe68..3a0cc74bef611175b82884bd87e521c5a968d54a 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -171,16 +171,6 @@ impl ExternalAgent { Self::Custom { name } => Rc::new(agent_servers::CustomAgentServer::new(name.clone())), } } - - pub fn is_mcp(&self) -> bool { - match self { - Self::Gemini => true, - Self::ClaudeCode => true, - Self::Codex => true, - Self::NativeAgent => false, - Self::Custom { .. } => false, - } - } } /// Opens the profile management interface for configuring agent tools and settings. diff --git a/crates/edit_prediction_cli/src/headless.rs b/crates/edit_prediction_cli/src/headless.rs index 489e78d364d0fdbb08b93eab89fd5f91f345f68e..da96e7ef6520e952e2b7696eee6b82c243e90e4e 100644 --- a/crates/edit_prediction_cli/src/headless.rs +++ b/crates/edit_prediction_cli/src/headless.rs @@ -114,7 +114,7 @@ pub fn init(cx: &mut App) -> EpAppState { tx.send(Some(options)).log_err(); }) .detach(); - let node_runtime = NodeRuntime::new(client.http_client(), None, rx, None); + let node_runtime = NodeRuntime::new(client.http_client(), None, rx); let extension_host_proxy = ExtensionHostProxy::global(cx); diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 3a2891922c80b95c85f0daed25603bea14b41842..80633696b7d5e655bb7db3627568b881642cf62c 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -422,7 +422,7 @@ pub fn init(cx: &mut App) -> Arc { tx.send(Some(options)).log_err(); }) .detach(); - let node_runtime = NodeRuntime::new(client.http_client(), None, rx, None); + let node_runtime = NodeRuntime::new(client.http_client(), None, rx); let extension_host_proxy = ExtensionHostProxy::global(cx); debug_adapter_extension::init(extension_host_proxy.clone(), cx); diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 322117cd717cac5c604ba215a2a1c7e0f7d87f06..1eb6714500446dbfd2967ed4aa2f514a5f427aba 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -9,8 +9,6 @@ use serde::Deserialize; use smol::io::BufReader; use smol::{fs, lock::Mutex}; use std::fmt::Display; -use std::future::Future; -use std::pin::Pin; use std::{ env::{self, consts}, ffi::OsString, @@ -48,7 +46,6 @@ struct NodeRuntimeState { last_options: Option, options: watch::Receiver>, shell_env_loaded: Shared>, - trust_task: Option + Send>>>, } impl NodeRuntime { @@ -56,11 +53,9 @@ impl NodeRuntime { http: Arc, shell_env_loaded: Option>, options: watch::Receiver>, - trust_task: Option + Send>>>, ) -> Self { NodeRuntime(Arc::new(Mutex::new(NodeRuntimeState { http, - trust_task, instance: None, last_options: None, options, @@ -75,15 +70,11 @@ impl NodeRuntime { last_options: None, options: watch::channel(Some(NodeBinaryOptions::default())).1, shell_env_loaded: oneshot::channel().1.shared(), - trust_task: None, }))) } async fn instance(&self) -> Box { let mut state = self.0.lock().await; - if let Some(trust_task) = state.trust_task.take() { - trust_task.await; - } let options = loop { if let Some(options) = state.options.borrow().as_ref() { diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 7d060db887b1b5d07dd4d6de9ca85297adfd0c6f..7ba46a46872ba57c758baccf9f67b0039818ee75 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -15,7 +15,6 @@ use util::{ResultExt as _, rel_path::RelPath}; use crate::{ Project, project_settings::{ContextServerSettings, ProjectSettings}, - trusted_worktrees::wait_for_workspace_trust, worktree_store::WorktreeStore, }; @@ -333,15 +332,6 @@ impl ContextServerStore { pub fn start_server(&mut self, server: Arc, cx: &mut Context) { cx.spawn(async move |this, cx| { - let wait_task = this.update(cx, |context_server_store, cx| { - context_server_store.project.update(cx, |project, cx| { - let remote_host = project.remote_connection_options(cx); - wait_for_workspace_trust(remote_host, "context servers", cx) - }) - })??; - if let Some(wait_task) = wait_task { - wait_task.await; - } let this = this.upgrade().context("Context server store dropped")?; let settings = this .update(cx, |this, _| { @@ -582,15 +572,6 @@ impl ContextServerStore { } async fn maintain_servers(this: WeakEntity, cx: &mut AsyncApp) -> Result<()> { - let wait_task = this.update(cx, |context_server_store, cx| { - context_server_store.project.update(cx, |project, cx| { - let remote_host = project.remote_connection_options(cx); - wait_for_workspace_trust(remote_host, "context servers", cx) - }) - })??; - if let Some(wait_task) = wait_task { - wait_task.await; - } let (mut configured_servers, registry, worktree_store) = this.update(cx, |this, _| { ( this.context_server_settings.clone(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bbf91eb3c18a53f32f48f1a044c991bf5cfd9fdf..dbab136d7831226c24a02925f722f1c99e150852 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4885,16 +4885,13 @@ impl Project { .update(|cx| TrustedWorktrees::try_get_global(cx))? .context("missing trusted worktrees")?; trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| { - let mut restricted_paths = envelope + let restricted_paths = envelope .payload .worktree_ids .into_iter() .map(WorktreeId::from_proto) .map(PathTrust::Worktree) .collect::>(); - if envelope.payload.restrict_workspace { - restricted_paths.insert(PathTrust::Workspace); - } let remote_host = this .read(cx) .remote_connection_options(cx) diff --git a/crates/project/src/trusted_worktrees.rs b/crates/project/src/trusted_worktrees.rs index d9f5d4a7a43d8cb8b8220f4d3de8ca35d366a8f5..cf88b0c06156d36a6117d35758b742336b1f6e0d 100644 --- a/crates/project/src/trusted_worktrees.rs +++ b/crates/project/src/trusted_worktrees.rs @@ -27,36 +27,20 @@ //! Spawning a language server is potentially dangerous, and Zed needs to restrict that by default. //! Each single file worktree requires a separate trust permission, unless a more global level is trusted. //! -//! * "workspace" -//! -//! Even an empty Zed instance with no files or directories open is potentially dangerous: opening an Assistant Panel and creating new external agent thread might require installing and running MCP servers. -//! -//! Disabling the entire panel is possible with ai-related settings. -//! Yet when it's enabled, it's still reasonably safe to use remote AI agents and control their permissions in the Assistant Panel. -//! -//! Unlike that, MCP servers are similar to language servers and may require fetching, installing and running packages or binaries. -//! Given that those servers are not tied to any particular worktree, this level of trust is required to operate any MCP server. -//! -//! Workspace level of trust assumes all single file worktrees are trusted too, for the same host: if we allow global MCP server-related functionality, we can already allow spawning language servers for single file worktrees as well. -//! //! * "directory worktree" //! //! If a directory is open in Zed, it's a full worktree which may spawn multiple language servers associated with it. //! Each such worktree requires a separate trust permission, so each separate directory worktree has to be trusted separately, unless a more global level is trusted. //! -//! When a directory worktree is trusted and language servers are allowed to be downloaded and started, hence we also allow workspace level of trust (hence, "single file worktree" level of trust also). +//! When a directory worktree is trusted and language servers are allowed to be downloaded and started, hence, "single file worktree" level of trust also. //! //! * "path override" //! //! To ease trusting multiple directory worktrees at once, it's possible to trust a parent directory of a certain directory worktree opened in Zed. //! Trusting a directory means trusting all its subdirectories as well, including all current and potential directory worktrees. -//! -//! If we trust multiple projects to install and spawn various language server processes, we can also allow workspace trust requests for MCP servers installation and spawning. use collections::{HashMap, HashSet}; -use gpui::{ - App, AppContext as _, Context, Entity, EventEmitter, Global, SharedString, Task, WeakEntity, -}; +use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, SharedString, WeakEntity}; use remote::RemoteConnectionOptions; use rpc::{AnyProtoClient, proto}; use settings::{Settings as _, WorktreeId}; @@ -132,57 +116,6 @@ pub fn track_worktree_trust( } } -/// Waits until at least [`PathTrust::Workspace`] level of trust is granted for the host the [`TrustedWorktrees`] was initialized with. -pub fn wait_for_default_workspace_trust( - what_waits: &'static str, - cx: &mut App, -) -> Option> { - let trusted_worktrees = TrustedWorktrees::try_get_global(cx)?; - wait_for_workspace_trust( - trusted_worktrees.read(cx).remote_host.clone(), - what_waits, - cx, - ) -} - -/// Waits until at least [`PathTrust::Workspace`] level of trust is granted for a particular host. -pub fn wait_for_workspace_trust( - remote_host: Option>, - what_waits: &'static str, - cx: &mut App, -) -> Option> { - let trusted_worktrees = TrustedWorktrees::try_get_global(cx)?; - let remote_host = remote_host.map(|host| host.into()); - - let remote_host = if trusted_worktrees.update(cx, |trusted_worktrees, cx| { - trusted_worktrees.can_trust_workspace(remote_host.clone(), cx) - }) { - None - } else { - Some(remote_host) - }?; - - Some(cx.spawn(async move |cx| { - log::info!("Waiting for workspace to be trusted before starting {what_waits}"); - let (tx, restricted_worktrees_task) = smol::channel::bounded::<()>(1); - let Ok(_subscription) = cx.update(|cx| { - cx.subscribe(&trusted_worktrees, move |_, e, _| { - if let TrustedWorktreesEvent::Trusted(trusted_host, trusted_paths) = e { - if trusted_host == &remote_host && trusted_paths.contains(&PathTrust::Workspace) - { - log::info!("Workspace is trusted for {what_waits}"); - tx.send_blocking(()).ok(); - } - } - }) - }) else { - return; - }; - - restricted_worktrees_task.recv().await.ok(); - })) -} - /// A collection of worktree trust metadata, can be accessed globally (if initialized) and subscribed to. pub struct TrustedWorktrees(Entity); @@ -205,8 +138,6 @@ pub struct TrustedWorktreesStore { worktree_stores: HashMap, Option>, trusted_paths: TrustedPaths, restricted: HashSet, - remote_host: Option, - restricted_workspaces: HashSet>, } /// An identifier of a host to split the trust questions by. @@ -246,9 +177,6 @@ impl From for RemoteHostLocation { /// See module-level documentation on the trust model. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum PathTrust { - /// General, no worktrees or files open case. - /// E.g. MCP servers can be spawned from a blank Zed instance, but will do `npm i` and other potentially malicious actions. - Workspace, /// A worktree that is familiar to this workspace. /// Either a single file or a directory worktree. Worktree(WorktreeId), @@ -260,9 +188,6 @@ pub enum PathTrust { impl PathTrust { fn to_proto(&self) -> proto::PathTrust { match self { - Self::Workspace => proto::PathTrust { - content: Some(proto::path_trust::Content::Workspace(0)), - }, Self::Worktree(worktree_id) => proto::PathTrust { content: Some(proto::path_trust::Content::WorktreeId( worktree_id.to_proto(), @@ -282,7 +207,6 @@ impl PathTrust { Self::Worktree(WorktreeId::from_proto(id)) } proto::path_trust::Content::AbsPath(path) => Self::AbsPath(PathBuf::from(path)), - proto::path_trust::Content::Workspace(_) => Self::Workspace, }) } } @@ -322,9 +246,7 @@ impl TrustedWorktreesStore { } let worktree_stores = match worktree_store { - Some(worktree_store) => { - HashMap::from_iter([(worktree_store.downgrade(), remote_host.clone())]) - } + Some(worktree_store) => HashMap::from_iter([(worktree_store.downgrade(), remote_host)]), None => HashMap::default(), }; @@ -332,8 +254,6 @@ impl TrustedWorktreesStore { trusted_paths, downstream_client, upstream_client, - remote_host, - restricted_workspaces: HashSet::default(), restricted: HashSet::default(), worktree_stores, } @@ -345,11 +265,9 @@ impl TrustedWorktreesStore { worktree_store: &Entity, cx: &App, ) -> bool { - let Some(remote_host) = self.worktree_stores.get(&worktree_store.downgrade()) else { - return false; - }; - self.restricted_workspaces.contains(remote_host) - || self.restricted.iter().any(|restricted_worktree| { + self.worktree_stores + .contains_key(&worktree_store.downgrade()) + && self.restricted.iter().any(|restricted_worktree| { worktree_store .read(cx) .worktree_for_id(*restricted_worktree, cx) @@ -366,7 +284,6 @@ impl TrustedWorktreesStore { remote_host: Option, cx: &mut Context, ) { - let mut new_workspace_trusted = false; let mut new_trusted_single_file_worktrees = HashSet::default(); let mut new_trusted_other_worktrees = HashSet::default(); let mut new_trusted_abs_paths = HashSet::default(); @@ -377,7 +294,6 @@ impl TrustedWorktreesStore { .flat_map(|current_trusted| current_trusted.iter()), ) { match trusted_path { - PathTrust::Workspace => new_workspace_trusted = true, PathTrust::Worktree(worktree_id) => { self.restricted.remove(worktree_id); if let Some((abs_path, is_file, host)) = @@ -388,13 +304,11 @@ impl TrustedWorktreesStore { new_trusted_single_file_worktrees.insert(*worktree_id); } else { new_trusted_other_worktrees.insert((abs_path, *worktree_id)); - new_workspace_trusted = true; } } } } PathTrust::AbsPath(path) => { - new_workspace_trusted = true; debug_assert!( path.is_absolute(), "Cannot trust non-absolute path {path:?}" @@ -404,11 +318,6 @@ impl TrustedWorktreesStore { } } - if new_workspace_trusted { - new_trusted_single_file_worktrees.clear(); - self.restricted_workspaces.remove(&remote_host); - trusted_paths.insert(PathTrust::Workspace); - } new_trusted_other_worktrees.retain(|(worktree_abs_path, _)| { new_trusted_abs_paths .iter() @@ -428,8 +337,7 @@ impl TrustedWorktreesStore { if restricted_host != remote_host { return true; } - let retain = (!is_file - || (!new_workspace_trusted && new_trusted_other_worktrees.is_empty())) + let retain = (!is_file || new_trusted_other_worktrees.is_empty()) && new_trusted_abs_paths.iter().all(|new_trusted_path| { !restricted_worktree_path.starts_with(new_trusted_path) }); @@ -453,9 +361,6 @@ impl TrustedWorktreesStore { .into_iter() .map(PathTrust::Worktree), ); - if trusted_paths.is_empty() && new_workspace_trusted { - trusted_paths.insert(PathTrust::Workspace); - } } cx.emit(TrustedWorktreesEvent::Trusted( @@ -489,13 +394,6 @@ impl TrustedWorktreesStore { ) { for restricted_path in restricted_paths { match restricted_path { - PathTrust::Workspace => { - self.restricted_workspaces.insert(remote_host.clone()); - cx.emit(TrustedWorktreesEvent::Restricted( - remote_host.clone(), - HashSet::from_iter([PathTrust::Workspace]), - )); - } PathTrust::Worktree(worktree_id) => { self.restricted.insert(worktree_id); cx.emit(TrustedWorktreesEvent::Restricted( @@ -568,7 +466,6 @@ impl TrustedWorktreesStore { downstream_client .send(proto::RestrictWorktrees { project_id: *downstream_project_id, - restrict_workspace: false, worktree_ids: vec![worktree_id.to_proto()], }) .ok(); @@ -577,7 +474,6 @@ impl TrustedWorktreesStore { upstream_client .send(proto::RestrictWorktrees { project_id: *upstream_project_id, - restrict_workspace: false, worktree_ids: vec![worktree_id.to_proto()], }) .ok(); @@ -585,61 +481,12 @@ impl TrustedWorktreesStore { false } - /// Checks whether a certain worktree is trusted globally (or on a larger trust level). - /// If not, emits [`TrustedWorktreesEvent::Restricted`] event if checked for the first time and not trusted. - /// - /// No events or data adjustment happens when `trust_all_worktrees` auto trust is enabled. - pub fn can_trust_workspace( - &mut self, - remote_host: Option, - cx: &mut Context, - ) -> bool { - if ProjectSettings::get_global(cx).session.trust_all_worktrees { - return true; - } - if self.restricted_workspaces.contains(&remote_host) { - return false; - } - if self.trusted_paths.contains_key(&remote_host) { - return true; - } - - self.restricted_workspaces.insert(remote_host.clone()); - cx.emit(TrustedWorktreesEvent::Restricted( - remote_host.clone(), - HashSet::from_iter([PathTrust::Workspace]), - )); - - if remote_host == self.remote_host { - if let Some((downstream_client, downstream_project_id)) = &self.downstream_client { - downstream_client - .send(proto::RestrictWorktrees { - project_id: *downstream_project_id, - restrict_workspace: true, - worktree_ids: Vec::new(), - }) - .ok(); - } - if let Some((upstream_client, upstream_project_id)) = &self.upstream_client { - upstream_client - .send(proto::RestrictWorktrees { - project_id: *upstream_project_id, - restrict_workspace: true, - worktree_ids: Vec::new(), - }) - .ok(); - } - } - false - } - - /// Lists all explicitly restricted worktrees (via [`TrustedWorktreesStore::can_trust`] and [`TrustedWorktreesStore::can_trust_workspace`] method calls) for a particular worktree store on a particular host. + /// Lists all explicitly restricted worktrees (via [`TrustedWorktreesStore::can_trust`] method calls) for a particular worktree store on a particular host. pub fn restricted_worktrees( &self, worktree_store: &WorktreeStore, - remote_host: Option, cx: &App, - ) -> HashSet)>> { + ) -> HashSet<(WorktreeId, Arc)> { let mut single_file_paths = HashSet::default(); let other_paths = self .restricted @@ -649,19 +496,16 @@ impl TrustedWorktreesStore { let worktree = worktree.read(cx); let abs_path = worktree.abs_path(); if worktree.is_single_file() { - single_file_paths.insert(Some((restricted_worktree_id, abs_path))); + single_file_paths.insert((restricted_worktree_id, abs_path)); None } else { Some((restricted_worktree_id, abs_path)) } }) - .map(Some) .collect::>(); if !other_paths.is_empty() { return other_paths; - } else if self.restricted_workspaces.contains(&remote_host) { - return HashSet::from_iter([None]); } else { single_file_paths } @@ -670,7 +514,7 @@ impl TrustedWorktreesStore { /// Switches the "trust nothing" mode to "automatically trust everything". /// This does not influence already persisted data, but stops adding new worktrees there. pub fn auto_trust_all(&mut self, cx: &mut Context) { - for (remote_host, mut worktrees) in std::mem::take(&mut self.restricted) + for (remote_host, worktrees) in std::mem::take(&mut self.restricted) .into_iter() .flat_map(|restricted_worktree| { let (_, _, host) = self.find_worktree_data(restricted_worktree, cx)?; @@ -683,26 +527,15 @@ impl TrustedWorktreesStore { acc }) { - if self.restricted_workspaces.remove(&remote_host) { - worktrees.insert(PathTrust::Workspace); - } self.trust(worktrees, remote_host, cx); } - - for remote_host in std::mem::take(&mut self.restricted_workspaces) { - self.trust(HashSet::from_iter([PathTrust::Workspace]), remote_host, cx); - } } /// Returns a normalized representation of the trusted paths to store in the DB. pub fn trusted_paths_for_serialization( &mut self, cx: &mut Context, - ) -> ( - HashSet>, - HashMap, HashSet>, - ) { - let mut new_trusted_workspaces = HashSet::default(); + ) -> HashMap, HashSet> { let new_trusted_worktrees = self .trusted_paths .clone() @@ -715,16 +548,12 @@ impl TrustedWorktreesStore { .find_worktree_data(worktree_id, cx) .map(|(abs_path, ..)| abs_path.to_path_buf()), PathTrust::AbsPath(abs_path) => Some(abs_path), - PathTrust::Workspace => { - new_trusted_workspaces.insert(host.clone()); - None - } }) .collect(); (host, abs_paths) }) .collect(); - (new_trusted_workspaces, new_trusted_worktrees) + new_trusted_worktrees } fn find_worktree_data( @@ -888,15 +717,9 @@ mod tests { assert!(has_restricted, "should have restricted worktrees"); let restricted = worktree_store.read_with(cx, |ws, cx| { - trusted_worktrees - .read(cx) - .restricted_worktrees(ws, None, cx) + trusted_worktrees.read(cx).restricted_worktrees(ws, cx) }); - assert!( - restricted - .iter() - .any(|r| r.as_ref().map(|(id, _)| *id) == Some(worktree_id)) - ); + assert!(restricted.iter().any(|(id, _)| *id == worktree_id)); events.borrow_mut().clear(); @@ -941,9 +764,7 @@ mod tests { ); let restricted_after = worktree_store.read_with(cx, |ws, cx| { - trusted_worktrees - .read(cx) - .restricted_worktrees(ws, None, cx) + trusted_worktrees.read(cx).restricted_worktrees(ws, cx) }); assert!( restricted_after.is_empty(), @@ -951,92 +772,6 @@ mod tests { ); } - #[gpui::test] - async fn test_workspace_trust_no_worktrees(cx: &mut TestAppContext) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({})).await; - - let project = Project::test(fs, Vec::<&Path>::new(), cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - let events: Rc>> = Rc::default(); - cx.update({ - let events = events.clone(); - |cx| { - cx.subscribe(&trusted_worktrees, move |_, event, _| { - events.borrow_mut().push(match event { - TrustedWorktreesEvent::Trusted(host, paths) => { - TrustedWorktreesEvent::Trusted(host.clone(), paths.clone()) - } - TrustedWorktreesEvent::Restricted(host, paths) => { - TrustedWorktreesEvent::Restricted(host.clone(), paths.clone()) - } - }); - }) - } - }) - .detach(); - - let can_trust_workspace = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - !can_trust_workspace, - "workspace should be restricted by default" - ); - - { - let events = events.borrow(); - assert_eq!(events.len(), 1); - match &events[0] { - TrustedWorktreesEvent::Restricted(host, paths) => { - assert!(host.is_none()); - assert!(paths.contains(&PathTrust::Workspace)); - } - _ => panic!("expected Restricted event"), - } - } - - events.borrow_mut().clear(); - - let can_trust_workspace_again = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - !can_trust_workspace_again, - "workspace should still be restricted" - ); - assert!( - events.borrow().is_empty(), - "no duplicate Restricted event on repeated can_trust_workspace" - ); - - trusted_worktrees.update(cx, |store, cx| { - store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx); - }); - - { - let events = events.borrow(); - assert_eq!(events.len(), 1); - match &events[0] { - TrustedWorktreesEvent::Trusted(host, paths) => { - assert!(host.is_none()); - assert!(paths.contains(&PathTrust::Workspace)); - } - _ => panic!("expected Trusted event"), - } - } - - let can_trust_workspace_after = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - can_trust_workspace_after, - "workspace should be trusted after trust()" - ); - } - #[gpui::test] async fn test_single_file_worktree_trust(cx: &mut TestAppContext) { init_test(cx); @@ -1122,58 +857,6 @@ mod tests { ); } - #[gpui::test] - async fn test_workspace_trust_unlocks_single_file_worktree(cx: &mut TestAppContext) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({ "foo.rs": "fn foo() {}" })) - .await; - - let project = Project::test(fs, [path!("/root/foo.rs").as_ref()], cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - let worktree_id = worktree_store.read_with(cx, |store, cx| { - let worktree = store.worktrees().next().unwrap(); - let worktree = worktree.read(cx); - assert!(worktree.is_single_file(), "expected single-file worktree"); - worktree.id() - }); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - let can_trust_workspace = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - !can_trust_workspace, - "workspace should be restricted by default" - ); - - let can_trust_file = - trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx)); - assert!( - !can_trust_file, - "single-file worktree should be restricted by default" - ); - - trusted_worktrees.update(cx, |store, cx| { - store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx); - }); - - let can_trust_workspace_after = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - can_trust_workspace_after, - "workspace should be trusted after trust(Workspace)" - ); - - let can_trust_file_after = - trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx)); - assert!( - can_trust_file_after, - "single-file worktree should be trusted after workspace trust" - ); - } - #[gpui::test] async fn test_multiple_single_file_worktrees_trust_one(cx: &mut TestAppContext) { init_test(cx); @@ -1319,47 +1002,6 @@ mod tests { assert!(can_trust_b, "project_b should now be trusted"); } - #[gpui::test] - async fn test_directory_worktree_trust_enables_workspace(cx: &mut TestAppContext) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" })) - .await; - - let project = Project::test(fs, [path!("/root").as_ref()], cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - let worktree_id = worktree_store.read_with(cx, |store, cx| { - let worktree = store.worktrees().next().unwrap(); - assert!(!worktree.read(cx).is_single_file()); - worktree.read(cx).id() - }); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - let can_trust_workspace = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - !can_trust_workspace, - "workspace should be restricted initially" - ); - - trusted_worktrees.update(cx, |store, cx| { - store.trust( - HashSet::from_iter([PathTrust::Worktree(worktree_id)]), - None, - cx, - ); - }); - - let can_trust_workspace_after = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - can_trust_workspace_after, - "workspace should be trusted after trusting directory worktree" - ); - } - #[gpui::test] async fn test_directory_worktree_trust_enables_single_file(cx: &mut TestAppContext) { init_test(cx); @@ -1428,7 +1070,7 @@ mod tests { let fs = FakeFs::new(cx.executor()); fs.insert_tree( - path!("/workspace"), + path!("/root"), json!({ "project_a": { "main.rs": "fn main() {}" }, "project_b": { "lib.rs": "pub fn lib() {}" } @@ -1439,8 +1081,8 @@ mod tests { let project = Project::test( fs, [ - path!("/workspace/project_a").as_ref(), - path!("/workspace/project_b").as_ref(), + path!("/root/project_a").as_ref(), + path!("/root/project_b").as_ref(), ], cx, ) @@ -1464,7 +1106,7 @@ mod tests { trusted_worktrees.update(cx, |store, cx| { store.trust( - HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/workspace")))]), + HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/root")))]), None, cx, ); @@ -1539,12 +1181,6 @@ mod tests { trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx)); assert!(!can_trust, "worktree should be restricted initially"); } - let can_trust_workspace = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - !can_trust_workspace, - "workspace should be restricted initially" - ); let has_restricted = trusted_worktrees.read_with(cx, |store, cx| { store.has_restricted_worktrees(&worktree_store, cx) @@ -1566,13 +1202,6 @@ mod tests { ); } - let can_trust_workspace = - trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx)); - assert!( - can_trust_workspace, - "workspace should be trusted after auto_trust_all" - ); - let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| { store.has_restricted_worktrees(&worktree_store, cx) }); @@ -1592,100 +1221,6 @@ mod tests { ); } - #[gpui::test] - async fn test_wait_for_global_trust_already_trusted(cx: &mut TestAppContext) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" })) - .await; - - let project = Project::test(fs, [path!("/root").as_ref()], cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - trusted_worktrees.update(cx, |store, cx| { - store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx); - }); - - let task = cx.update(|cx| wait_for_workspace_trust(None::, "test", cx)); - assert!(task.is_none(), "should return None when already trusted"); - } - - #[gpui::test] - async fn test_wait_for_workspace_trust_resolves_on_trust(cx: &mut TestAppContext) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" })) - .await; - - let project = Project::test(fs, [path!("/root").as_ref()], cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - let task = cx.update(|cx| wait_for_workspace_trust(None::, "test", cx)); - assert!( - task.is_some(), - "should return Some(Task) when not yet trusted" - ); - - let task = task.unwrap(); - - cx.executor().run_until_parked(); - - trusted_worktrees.update(cx, |store, cx| { - store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx); - }); - - cx.executor().run_until_parked(); - task.await; - } - - #[gpui::test] - async fn test_wait_for_default_workspace_trust_resolves_on_directory_worktree_trust( - cx: &mut TestAppContext, - ) { - init_test(cx); - - let fs = FakeFs::new(cx.executor()); - fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" })) - .await; - - let project = Project::test(fs, [path!("/root").as_ref()], cx).await; - let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); - let worktree_id = worktree_store.read_with(cx, |store, cx| { - let worktree = store.worktrees().next().unwrap(); - assert!(!worktree.read(cx).is_single_file()); - worktree.read(cx).id() - }); - - let trusted_worktrees = init_trust_global(worktree_store, cx); - - let task = cx.update(|cx| wait_for_default_workspace_trust("test", cx)); - assert!( - task.is_some(), - "should return Some(Task) when not yet trusted" - ); - - let task = task.unwrap(); - - cx.executor().run_until_parked(); - - trusted_worktrees.update(cx, |store, cx| { - store.trust( - HashSet::from_iter([PathTrust::Worktree(worktree_id)]), - None, - cx, - ); - }); - - cx.executor().run_until_parked(); - task.await; - } - #[gpui::test] async fn test_trust_restrict_trust_cycle(cx: &mut TestAppContext) { init_test(cx); @@ -1820,36 +1355,11 @@ mod tests { let trusted_worktrees = init_trust_global(worktree_store, cx); let host_a: Option = None; - let host_b = Some(RemoteHostLocation { - user_name: Some("user".into()), - host_identifier: "remote-host".into(), - }); let can_trust_local = trusted_worktrees.update(cx, |store, cx| store.can_trust(local_worktree, cx)); assert!(!can_trust_local, "local worktree restricted on host_a"); - trusted_worktrees.update(cx, |store, cx| { - store.trust( - HashSet::from_iter([PathTrust::Workspace]), - host_b.clone(), - cx, - ); - }); - - let can_trust_workspace_a = trusted_worktrees.update(cx, |store, cx| { - store.can_trust_workspace(host_a.clone(), cx) - }); - assert!( - !can_trust_workspace_a, - "host_a workspace should still be restricted" - ); - - let can_trust_workspace_b = trusted_worktrees.update(cx, |store, cx| { - store.can_trust_workspace(host_b.clone(), cx) - }); - assert!(can_trust_workspace_b, "host_b workspace should be trusted"); - trusted_worktrees.update(cx, |store, cx| { store.trust( HashSet::from_iter([PathTrust::Worktree(local_worktree)]), @@ -1864,13 +1374,5 @@ mod tests { can_trust_local_after, "local worktree should be trusted on host_a" ); - - let can_trust_workspace_a_after = trusted_worktrees.update(cx, |store, cx| { - store.can_trust_workspace(host_a.clone(), cx) - }); - assert!( - can_trust_workspace_a_after, - "host_a workspace should be trusted after directory trust" - ); } } diff --git a/crates/project_benchmarks/src/main.rs b/crates/project_benchmarks/src/main.rs index 03c25bc464af06793e351f27588b023ec8eb3eb9..e4ddbb6cf2c7b6984df2533963bdf6bf88eacba0 100644 --- a/crates/project_benchmarks/src/main.rs +++ b/crates/project_benchmarks/src/main.rs @@ -62,7 +62,7 @@ fn main() -> Result<(), anyhow::Error> { let client = Client::production(cx); let http_client = FakeHttpClient::with_200_response(); let (_, rx) = watch::channel(None); - let node = NodeRuntime::new(http_client, None, rx, None); + let node = NodeRuntime::new(http_client, None, rx); let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); let registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone())); let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())); diff --git a/crates/proto/proto/worktree.proto b/crates/proto/proto/worktree.proto index 315aeb311e1e4284970dffa17bee4b0142373e92..5873cfc10c1c6af24520705c27781b916dfda3d0 100644 --- a/crates/proto/proto/worktree.proto +++ b/crates/proto/proto/worktree.proto @@ -166,14 +166,16 @@ message TrustWorktrees { message PathTrust { oneof content { - uint64 workspace = 1; uint64 worktree_id = 2; string abs_path = 3; } + + reserved 1; } message RestrictWorktrees { uint64 project_id = 1; - bool restrict_workspace = 2; repeated uint64 worktree_ids = 3; + + reserved 2; } diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 89d26d35c77e076e1e618669acb5e54dc8afdcca..c83cc6aa34402a082fe104d64a8cb47f460704b8 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -642,16 +642,13 @@ impl HeadlessProject { .update(|cx| TrustedWorktrees::try_get_global(cx))? .context("missing trusted worktrees")?; trusted_worktrees.update(&mut cx, |trusted_worktrees, cx| { - let mut restricted_paths = envelope + let restricted_paths = envelope .payload .worktree_ids .into_iter() .map(WorktreeId::from_proto) .map(PathTrust::Worktree) .collect::>(); - if envelope.payload.restrict_workspace { - restricted_paths.insert(PathTrust::Workspace); - } trusted_worktrees.restrict(restricted_paths, None, cx); })?; Ok(proto::Ack {}) diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index af603998171e19d4776d47479ff81aa08d26d258..449b8491ece2494dacf8bfb1fa89aeeb8f6a81ac 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -36,7 +36,6 @@ use smol::Async; use smol::channel::{Receiver, Sender}; use smol::io::AsyncReadExt; use smol::{net::unix::UnixListener, stream::StreamExt as _}; -use std::pin::Pin; use std::{ env, ffi::OsStr, @@ -453,13 +452,10 @@ pub fn execute_run( ) }; - let trust_task = trusted_worktrees::wait_for_default_workspace_trust("Node runtime", cx) - .map(|trust_task| Box::pin(trust_task) as Pin>); let node_runtime = NodeRuntime::new( http_client.clone(), Some(shell_env_loaded_rx), node_settings_rx, - trust_task, ); let mut languages = LanguageRegistry::new(cx.background_executor().clone()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index dc113db68e33dc527e6b8d2cb66f644bcd83b661..cb37a86f25d5c0f22787f1e4a5b6fc3ad2d46893 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -1817,7 +1817,6 @@ impl WorkspaceDb { pub(crate) async fn save_trusted_worktrees( &self, trusted_worktrees: HashMap, HashSet>, - trusted_workspaces: HashSet>, ) -> anyhow::Result<()> { use anyhow::Context as _; use db::sqlez::statement::Statement; @@ -1834,7 +1833,6 @@ impl WorkspaceDb { .into_iter() .map(move |abs_path| (Some(abs_path), host.clone())) }) - .chain(trusted_workspaces.into_iter().map(|host| (None, host))) .collect::>(); let mut first_worktree; let mut last_worktree = 0_usize; @@ -1899,7 +1897,7 @@ VALUES {placeholders};"# let trusted_worktrees = DB.trusted_worktrees()?; Ok(trusted_worktrees .into_iter() - .map(|(abs_path, user_name, host_name)| { + .filter_map(|(abs_path, user_name, host_name)| { let db_host = match (user_name, host_name) { (_, None) => None, (None, Some(host_name)) => Some(RemoteHostLocation { @@ -1912,21 +1910,17 @@ VALUES {placeholders};"# }), }; - match abs_path { - Some(abs_path) => { - if db_host != host { - (db_host, PathTrust::AbsPath(abs_path)) - } else if let Some(worktree_store) = &worktree_store { - find_worktree_in_store(worktree_store.read(cx), &abs_path, cx) - .map(PathTrust::Worktree) - .map(|trusted_worktree| (host.clone(), trusted_worktree)) - .unwrap_or_else(|| (db_host.clone(), PathTrust::AbsPath(abs_path))) - } else { - (db_host, PathTrust::AbsPath(abs_path)) - } - } - None => (db_host, PathTrust::Workspace), - } + let abs_path = abs_path?; + Some(if db_host != host { + (db_host, PathTrust::AbsPath(abs_path)) + } else if let Some(worktree_store) = &worktree_store { + find_worktree_in_store(worktree_store.read(cx), &abs_path, cx) + .map(PathTrust::Worktree) + .map(|trusted_worktree| (host.clone(), trusted_worktree)) + .unwrap_or_else(|| (db_host.clone(), PathTrust::AbsPath(abs_path))) + } else { + (db_host, PathTrust::AbsPath(abs_path)) + }) }) .fold(HashMap::default(), |mut acc, (remote_host, path_trust)| { acc.entry(remote_host) diff --git a/crates/workspace/src/security_modal.rs b/crates/workspace/src/security_modal.rs index 1b5509d4d64e5b1377c9675fb49d2981e8173668..e3b9ab6e72481048d0f78eb07afb72af53810279 100644 --- a/crates/workspace/src/security_modal.rs +++ b/crates/workspace/src/security_modal.rs @@ -23,7 +23,7 @@ use ui::{ use crate::{DismissDecision, ModalView, ToggleWorktreeSecurity}; pub struct SecurityModal { - restricted_paths: HashMap, RestrictedPath>, + restricted_paths: HashMap, home_dir: Option, trust_parents: bool, worktree_store: WeakEntity, @@ -34,7 +34,7 @@ pub struct SecurityModal { #[derive(Debug, PartialEq, Eq)] struct RestrictedPath { - abs_path: Option>, + abs_path: Arc, is_file: bool, host: Option, } @@ -103,13 +103,11 @@ impl Render for SecurityModal { .child(Label::new(header_label)), ) .children(self.restricted_paths.values().map(|restricted_path| { - let abs_path = restricted_path.abs_path.as_ref().and_then(|abs_path| { - if restricted_path.is_file { - abs_path.parent() - } else { - Some(abs_path.as_ref()) - } - }); + let abs_path = if restricted_path.is_file { + restricted_path.abs_path.parent() + } else { + Some(restricted_path.abs_path.as_ref()) + }; let label = match abs_path { Some(abs_path) => match &restricted_path.host { @@ -254,7 +252,7 @@ impl SecurityModal { has_restricted_files |= restricted_path.is_file; !restricted_path.is_file }) - .filter_map(|restricted_path| restricted_path.abs_path.as_ref()?.parent()) + .filter_map(|restricted_path| restricted_path.abs_path.parent()) .collect::>(); match available_parents.len() { 0 => { @@ -289,19 +287,17 @@ impl SecurityModal { let mut paths_to_trust = self .restricted_paths .keys() - .map(|worktree_id| match worktree_id { - Some(worktree_id) => PathTrust::Worktree(*worktree_id), - None => PathTrust::Workspace, - }) + .copied() + .map(PathTrust::Worktree) .collect::>(); if self.trust_parents { paths_to_trust.extend(self.restricted_paths.values().filter_map( |restricted_paths| { if restricted_paths.is_file { - Some(PathTrust::Workspace) + None } else { let parent_abs_path = - restricted_paths.abs_path.as_ref()?.parent()?.to_owned(); + restricted_paths.abs_path.parent()?.to_owned(); Some(PathTrust::AbsPath(parent_abs_path)) } }, @@ -322,42 +318,22 @@ impl SecurityModal { pub fn refresh_restricted_paths(&mut self, cx: &mut Context) { if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) { if let Some(worktree_store) = self.worktree_store.upgrade() { - let mut new_restricted_worktrees = trusted_worktrees + let new_restricted_worktrees = trusted_worktrees .read(cx) - .restricted_worktrees(worktree_store.read(cx), self.remote_host.clone(), cx) + .restricted_worktrees(worktree_store.read(cx), cx) .into_iter() - .filter_map(|restricted_path| { - let restricted_path = match restricted_path { - Some((worktree_id, abs_path)) => { - let worktree = - worktree_store.read(cx).worktree_for_id(worktree_id, cx)?; - ( - Some(worktree_id), - RestrictedPath { - abs_path: Some(abs_path), - is_file: worktree.read(cx).is_single_file(), - host: self.remote_host.clone(), - }, - ) - } - None => ( - None, - RestrictedPath { - abs_path: None, - is_file: false, - host: self.remote_host.clone(), - }, - ), - }; - Some(restricted_path) + .filter_map(|(worktree_id, abs_path)| { + let worktree = worktree_store.read(cx).worktree_for_id(worktree_id, cx)?; + Some(( + worktree_id, + RestrictedPath { + abs_path, + is_file: worktree.read(cx).is_single_file(), + host: self.remote_host.clone(), + }, + )) }) .collect::>(); - // Do not clutter the UI: - // * trusting regular local worktrees assumes the workspace is trusted either, on the same host. - // * trusting a workspace trusts all single-file worktrees on the same host. - if new_restricted_worktrees.len() > 1 { - new_restricted_worktrees.remove(&None); - } if self.restricted_paths != new_restricted_worktrees { self.trust_parents = false; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5779152ecc46c9ee98f9433a47fa65fc2d9d4df5..80d41a0779794dc49b0e5041bb8733f9816f3b65 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1237,21 +1237,18 @@ impl Workspace { if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) { cx.subscribe(&trusted_worktrees, |workspace, worktrees_store, e, cx| { if let TrustedWorktreesEvent::Trusted(..) = e { - let (new_trusted_workspaces, new_trusted_worktrees) = worktrees_store - .update(cx, |worktrees_store, cx| { - worktrees_store.trusted_paths_for_serialization(cx) - }); // Do not persist auto trusted worktrees if !ProjectSettings::get_global(cx).session.trust_all_worktrees { + let new_trusted_worktrees = + worktrees_store.update(cx, |worktrees_store, cx| { + worktrees_store.trusted_paths_for_serialization(cx) + }); let timeout = cx.background_executor().timer(SERIALIZATION_THROTTLE_TIME); workspace._schedule_serialize_worktree_trust = cx.background_spawn(async move { timeout.await; persistence::DB - .save_trusted_worktrees( - new_trusted_worktrees, - new_trusted_workspaces, - ) + .save_trusted_worktrees(new_trusted_worktrees) .await .log_err(); }); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c8137a71c0f2a8524f6310d7cd711978ed833d1a..2db32547da75ae284a1592572414cb57b1ec97d1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -36,7 +36,6 @@ use std::{ env, io::{self, IsTerminal}, path::{Path, PathBuf}, - pin::Pin, process, sync::{Arc, OnceLock}, time::Instant, @@ -484,14 +483,7 @@ pub fn main() { }) .detach(); - let trust_task = trusted_worktrees::wait_for_default_workspace_trust("Node runtime", cx) - .map(|trust_task| Box::pin(trust_task) as Pin>); - let node_runtime = NodeRuntime::new( - client.http_client(), - Some(shell_env_loaded_rx), - rx, - trust_task, - ); + let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx); debug_adapter_extension::init(extension_host_proxy.clone(), cx); languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx); diff --git a/docs/src/worktree-trust.md b/docs/src/worktree-trust.md index 158851117bfdc4d00746594d74e1e6dae0bb84dc..590f063a75ac5d77e60d50f03af4795d6ec2961f 100644 --- a/docs/src/worktree-trust.md +++ b/docs/src/worktree-trust.md @@ -4,11 +4,11 @@ A worktree in Zed is either a directory or a single file that Zed opens as a sta Zed opens a worktree every time `zed some/path` is invoked, on drag and dropping a file or directory into Zed, on opening user settings.json, etc. Every worktree opened may contain a `.zed/settings.json` file with extra configuration options that may require installing and spawning language servers or MCP servers. -Note that the Zed workspace itself may also perform user-configured MCP server installation and spawning, even if no worktrees are open. +In order to provide users the opportunity to make their own choices according to their unique threat model and risk tolerance, all worktrees will be started in Restricted mode, which prevents download and execution of any related items from `.zed/settings.json`. Until configured to trust the worktree(s), Zed will not perform any related untrusted actions and will wait for user confirmation. This gives users a chance to review and understand any pre-configured settings, MCP servers, or language servers associated with a project. -In order to provide users the opportunity to make their own choices according to their unique threat model and risk tolerance, the workspace and all worktrees will be started in Restricted mode, which prevents download and execution of any related items. Until configured to trust the workspace and/or worktrees, Zed will not perform any untrusted actions and will wait for user confirmation. This gives users a chance to review and understand any pre-configured settings, MCP servers, or language servers associated with a project. +Note that at this point, Zed trusts the tools it installs itself, hence global entities such as global MCP servers, language servers like prettier and copilot are still in installed and started as usual, independent of worktree trust. -If a worktree is not trusted, Zed will indicate this with an exclamation mark icon in the title bar and a message in the Agent panel. Clicking this icon or using `workspace::ToggleWorktreeSecurity` action will bring up the security modal that allows the user to trust the worktree. +If a worktree is not trusted, Zed will indicate this with an exclamation mark icon in the title bar. Clicking this icon or using `workspace::ToggleWorktreeSecurity` action will bring up the security modal that allows the user to trust the worktree. Trusting any worktree will persist this information between restarts. It's possible to clear all trusted worktrees with `workspace::ClearTrustedWorktrees` command. This command will restart Zed, to ensure no untrusted settings, language servers or MCP servers persist. @@ -25,7 +25,7 @@ Restricted Mode prevents: ## Configuring broad worktree trust -By default, Zed won't trust any new worktrees and users will be required to trust each new worktree. Though not recommended, users may elect to trust all worktrees and the current workspace for a given session by configuring the following setting: +By default, Zed won't trust any new worktrees and users will be required to trust each new worktree. Though not recommended, users may elect to trust all worktrees by configuring the following setting: ```json [settings] "session": { @@ -47,20 +47,12 @@ A typical scenario where a directory might be open and a single file is subseque Spawning a language server presents a risk should the language server experience a supply-chain attack; therefore, Zed restricts that by default. Each single file worktree requires a separate trust grant, unless the directory containing it is trusted or all worktrees are trusted. -- "workspace" - -Even an empty Zed workspace with no files or directories open presents a risk if new MCP servers are locally configured by the user without review. For instance, opening an Assistant Panel and creating a new external agent thread might require installing and running new user-configured [Model Context Protocol servers](./ai/mcp.md). By default, zed will restrict a new MCP server until the user elects to trust the local workspace. Users may also disable the entire Agent panel if preferred; see [AI Configuration](./ai/configuration.md) for more details. - -Workspace trust, permitted by trusting Zed with no worktrees open, allows locally configured resources to be downloaded and executed. Workspace trust is per host and also trusts all single file worktrees from the same host in order to permit all local user-configured MCP and language servers to start. - - "directory worktree" If a directory is open in Zed, it's a full worktree which may spawn multiple language servers associated with it or spawn MCP servers if contained in a project settings file.Therefore, each directory worktree requires a separate trust grant unless a parent directory worktree trust is granted (see below). -When a directory worktree is trusted, language and MCP servers are permitted to be downloaded and started, hence we also enable workspace trust for the host in question automatically when this occurs. +When a directory worktree is trusted, language and MCP servers are permitted to be downloaded and started, hence we also enable single file worktree trust for the host in question automatically when this occurs: this helps when opening single files when using language server features in the trusted directory worktree. - "parent directory worktree" To permit trust decisions for multiple directory worktrees at once, it's possible to trust all subdirectories of a given parent directory worktree opened in Zed by checking the appropriate checkbox. This will grant trust to all its subdirectories, including all current and potential directory worktrees. - -This also automatically enables workspace trust to permit the newly trusted resources to download and start.