Detailed changes
@@ -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<ExternalAgent> for AgentType {
@@ -455,9 +443,7 @@ pub struct AgentPanel {
pending_serialization: Option<Task<Result<()>>>,
onboarding: Entity<AgentPanelOnboarding>,
selected_agent: AgentType,
- new_agent_thread_task: Task<()>,
show_trust_workspace_message: bool,
- _worktree_trust_subscription: Option<Subscription>,
}
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<Self>,
) {
- 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<Self>) {
match agent {
AgentType::TextThread => {
window.dispatch_action(NewTextThread.boxed_clone(), cx);
@@ -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.
@@ -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);
@@ -422,7 +422,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
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);
@@ -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<NodeBinaryOptions>,
options: watch::Receiver<Option<NodeBinaryOptions>>,
shell_env_loaded: Shared<oneshot::Receiver<()>>,
- trust_task: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
}
impl NodeRuntime {
@@ -56,11 +53,9 @@ impl NodeRuntime {
http: Arc<dyn HttpClient>,
shell_env_loaded: Option<oneshot::Receiver<()>>,
options: watch::Receiver<Option<NodeBinaryOptions>>,
- trust_task: Option<Pin<Box<dyn Future<Output = ()> + 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<dyn NodeRuntimeTrait> {
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() {
@@ -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<ContextServer>, cx: &mut Context<Self>) {
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<Self>, 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(),
@@ -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::<HashSet<_>>();
- if envelope.payload.restrict_workspace {
- restricted_paths.insert(PathTrust::Workspace);
- }
let remote_host = this
.read(cx)
.remote_connection_options(cx)
@@ -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<Task<()>> {
- 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<impl Into<RemoteHostLocation>>,
- what_waits: &'static str,
- cx: &mut App,
-) -> Option<Task<()>> {
- 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<TrustedWorktreesStore>);
@@ -205,8 +138,6 @@ pub struct TrustedWorktreesStore {
worktree_stores: HashMap<WeakEntity<WorktreeStore>, Option<RemoteHostLocation>>,
trusted_paths: TrustedPaths,
restricted: HashSet<WorktreeId>,
- remote_host: Option<RemoteHostLocation>,
- restricted_workspaces: HashSet<Option<RemoteHostLocation>>,
}
/// An identifier of a host to split the trust questions by.
@@ -246,9 +177,6 @@ impl From<RemoteConnectionOptions> 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<WorktreeStore>,
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<RemoteHostLocation>,
cx: &mut Context<Self>,
) {
- 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<RemoteHostLocation>,
- cx: &mut Context<Self>,
- ) -> 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<RemoteHostLocation>,
cx: &App,
- ) -> HashSet<Option<(WorktreeId, Arc<Path>)>> {
+ ) -> HashSet<(WorktreeId, Arc<Path>)> {
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::<HashSet<_>>();
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<Self>) {
- 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<Self>,
- ) -> (
- HashSet<Option<RemoteHostLocation>>,
- HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>,
- ) {
- let mut new_trusted_workspaces = HashSet::default();
+ ) -> HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>> {
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<RefCell<Vec<TrustedWorktreesEvent>>> = 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::<RemoteHostLocation>, "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::<RemoteHostLocation>, "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<RemoteHostLocation> = 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"
- );
}
}
@@ -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()));
@@ -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;
}
@@ -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::<HashSet<_>>();
- if envelope.payload.restrict_workspace {
- restricted_paths.insert(PathTrust::Workspace);
- }
trusted_worktrees.restrict(restricted_paths, None, cx);
})?;
Ok(proto::Ack {})
@@ -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<Box<_>>);
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());
@@ -1817,7 +1817,6 @@ impl WorkspaceDb {
pub(crate) async fn save_trusted_worktrees(
&self,
trusted_worktrees: HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>,
- trusted_workspaces: HashSet<Option<RemoteHostLocation>>,
) -> 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::<Vec<_>>();
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)
@@ -23,7 +23,7 @@ use ui::{
use crate::{DismissDecision, ModalView, ToggleWorktreeSecurity};
pub struct SecurityModal {
- restricted_paths: HashMap<Option<WorktreeId>, RestrictedPath>,
+ restricted_paths: HashMap<WorktreeId, RestrictedPath>,
home_dir: Option<PathBuf>,
trust_parents: bool,
worktree_store: WeakEntity<WorktreeStore>,
@@ -34,7 +34,7 @@ pub struct SecurityModal {
#[derive(Debug, PartialEq, Eq)]
struct RestrictedPath {
- abs_path: Option<Arc<Path>>,
+ abs_path: Arc<Path>,
is_file: bool,
host: Option<RemoteHostLocation>,
}
@@ -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::<SmallVec<[_; 2]>>();
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::<HashSet<_>>();
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<Self>) {
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::<HashMap<_, _>>();
- // 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;
@@ -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();
});
@@ -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<Box<_>>);
- 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);
@@ -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.