extension_host: Load granted extension capabilities from settings (#39472)
This PR adds the ability to control the capabilities granted to extensions by the extension host via the new `granted_extension_capabilities` setting. This setting is a list of the capabilities granted to any extension running in Zed. The currently available capabilities are: - `process:exec` - Grants extensions the ability to invoke commands using [`zed_extension_api::process::Command`](https://docs.rs/zed_extension_api/latest/zed_extension_api/process/struct.Command.html) - `download_file` - Grants extensions the ability to download files using [`zed_extension_api::download_file`](https://docs.rs/zed_extension_api/latest/zed_extension_api/fn.download_file.html) - `npm:install` - Grants extensions the ability to install npm packages using [`zed_extension_api::npm_install_package`](https://docs.rs/zed_extension_api/latest/zed_extension_api/fn.npm_install_package.html) Each of these capabilities has parameters that can be used to customize the permissions. For instance, to only allow downloads from GitHub, the `download_file` capability can specify an allowed `host`: ```json [ { "kind": "download_file", "host": "github.com", "path": ["**"] } ] ``` The same capability can also be granted multiple times with different parameters to build up an allowlist: ```json [ { "kind": "download_file", "host": "github.com", "path": ["**"] }, { "kind": "download_file", "host": "gitlab.com", "path": ["**"] } ] ``` When an extension is not granted a capability, the associated extension APIs protected by that capability will fail. For instance, trying to use `zed_extension_api::download_file` when the `download_file` capability is not granted will result in an error that will be surfaced by the extension: ``` Language server phpactor: from extension "PHP" version 0.4.3: failed to download file: capability for download_file https://github.com/phpactor/phpactor/releases/download/2025.07.25.0/phpactor.phar is not granted by the extension host ``` Release Notes: - Added a `granted_extension_capabilities` setting to control the capabilities granted to extensions.
This commit is contained in:
@@ -1562,6 +1562,14 @@
|
||||
"auto_install_extensions": {
|
||||
"html": true
|
||||
},
|
||||
// The capabilities granted to extensions.
|
||||
//
|
||||
// This list can be customized to restrict what extensions are able to do.
|
||||
"granted_extension_capabilities": [
|
||||
{ "kind": "process:exec", "command": "*", "args": ["**"] },
|
||||
{ "kind": "download_file", "host": "*", "path": ["**"] },
|
||||
{ "kind": "npm:install", "package": "*" }
|
||||
],
|
||||
// Controls how completions are processed for this language.
|
||||
"completions": {
|
||||
// Controls how words are completed.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use collections::HashMap;
|
||||
use extension::{
|
||||
DownloadFileCapability, ExtensionCapability, NpmInstallPackageCapability, ProcessExecCapability,
|
||||
};
|
||||
use gpui::App;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
@@ -13,6 +16,7 @@ pub struct ExtensionSettings {
|
||||
/// Default: { "html": true }
|
||||
pub auto_install_extensions: HashMap<Arc<str>, bool>,
|
||||
pub auto_update_extensions: HashMap<Arc<str>, bool>,
|
||||
pub granted_capabilities: Vec<ExtensionCapability>,
|
||||
}
|
||||
|
||||
impl ExtensionSettings {
|
||||
@@ -37,6 +41,32 @@ impl Settings for ExtensionSettings {
|
||||
Self {
|
||||
auto_install_extensions: content.extension.auto_install_extensions.clone(),
|
||||
auto_update_extensions: content.extension.auto_update_extensions.clone(),
|
||||
granted_capabilities: content
|
||||
.extension
|
||||
.granted_extension_capabilities
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|capability| match capability {
|
||||
settings::ExtensionCapabilityContent::ProcessExec(capability) => {
|
||||
ExtensionCapability::ProcessExec(ProcessExecCapability {
|
||||
command: capability.command,
|
||||
args: capability.args,
|
||||
})
|
||||
}
|
||||
settings::ExtensionCapabilityContent::DownloadFile(capability) => {
|
||||
ExtensionCapability::DownloadFile(DownloadFileCapability {
|
||||
host: capability.host,
|
||||
path: capability.path,
|
||||
})
|
||||
}
|
||||
settings::ExtensionCapabilityContent::NpmInstallPackage(capability) => {
|
||||
ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability {
|
||||
package: capability.package,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
pub mod wit;
|
||||
|
||||
use crate::ExtensionManifest;
|
||||
use crate::capability_granter::CapabilityGranter;
|
||||
use crate::{ExtensionManifest, ExtensionSettings};
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use async_trait::async_trait;
|
||||
use dap::{DebugRequest, StartDebuggingRequestArgumentsRequest};
|
||||
use extension::{
|
||||
CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary,
|
||||
DebugTaskDefinition, DownloadFileCapability, ExtensionCapability, ExtensionHostProxy,
|
||||
KeyValueStoreDelegate, NpmInstallPackageCapability, ProcessExecCapability, ProjectDelegate,
|
||||
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
|
||||
DebugTaskDefinition, ExtensionCapability, ExtensionHostProxy, KeyValueStoreDelegate,
|
||||
ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol,
|
||||
WorktreeDelegate,
|
||||
};
|
||||
use fs::{Fs, normalize_path};
|
||||
use futures::future::LocalBoxFuture;
|
||||
@@ -29,6 +29,7 @@ use moka::sync::Cache;
|
||||
use node_runtime::NodeRuntime;
|
||||
use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
use settings::Settings;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
use std::time::Duration;
|
||||
@@ -569,6 +570,9 @@ impl WasmHost {
|
||||
message(cx).await;
|
||||
}
|
||||
});
|
||||
|
||||
let extension_settings = ExtensionSettings::get_global(cx);
|
||||
|
||||
Arc::new(Self {
|
||||
engine: wasm_engine(cx.background_executor()),
|
||||
fs,
|
||||
@@ -577,19 +581,7 @@ impl WasmHost {
|
||||
node_runtime,
|
||||
proxy,
|
||||
release_channel: ReleaseChannel::global(cx),
|
||||
granted_capabilities: vec![
|
||||
ExtensionCapability::ProcessExec(ProcessExecCapability {
|
||||
command: "*".to_string(),
|
||||
args: vec!["**".to_string()],
|
||||
}),
|
||||
ExtensionCapability::DownloadFile(DownloadFileCapability {
|
||||
host: "*".to_string(),
|
||||
path: vec!["**".to_string()],
|
||||
}),
|
||||
ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability {
|
||||
package: "*".to_string(),
|
||||
}),
|
||||
],
|
||||
granted_capabilities: extension_settings.granted_capabilities.clone(),
|
||||
_main_thread_message_task: task,
|
||||
main_thread_message_tx: tx,
|
||||
})
|
||||
|
||||
@@ -26,7 +26,7 @@ use rpc::{
|
||||
proto::{self, REMOTE_SERVER_PEER_ID, REMOTE_SERVER_PROJECT_ID},
|
||||
};
|
||||
|
||||
use settings::initial_server_settings_content;
|
||||
use settings::{Settings as _, initial_server_settings_content};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@@ -69,6 +69,7 @@ impl HeadlessProject {
|
||||
settings::init(cx);
|
||||
language::init(cx);
|
||||
project::Project::init_settings(cx);
|
||||
extension_host::ExtensionSettings::register(cx);
|
||||
log_store::init(true, cx);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod agent;
|
||||
mod editor;
|
||||
mod extension;
|
||||
mod language;
|
||||
mod language_model;
|
||||
mod project;
|
||||
@@ -9,6 +10,7 @@ mod workspace;
|
||||
|
||||
pub use agent::*;
|
||||
pub use editor::*;
|
||||
pub use extension::*;
|
||||
pub use language::*;
|
||||
pub use language_model::*;
|
||||
pub use project::*;
|
||||
@@ -433,21 +435,6 @@ pub struct CallSettingsContent {
|
||||
pub share_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema, MergeFrom)]
|
||||
pub struct ExtensionSettingsContent {
|
||||
/// The extensions that should be automatically installed by Zed.
|
||||
///
|
||||
/// This is used to make functionality provided by extensions (e.g., language support)
|
||||
/// available out-of-the-box.
|
||||
///
|
||||
/// Default: { "html": true }
|
||||
#[serde(default)]
|
||||
pub auto_install_extensions: HashMap<Arc<str>, bool>,
|
||||
#[serde(default)]
|
||||
pub auto_update_extensions: HashMap<Arc<str>, bool>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
|
||||
pub struct GitPanelSettingsContent {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use settings_macros::MergeFrom;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
pub struct ExtensionSettingsContent {
|
||||
/// The extensions that should be automatically installed by Zed.
|
||||
///
|
||||
/// This is used to make functionality provided by extensions (e.g., language support)
|
||||
/// available out-of-the-box.
|
||||
///
|
||||
/// Default: { "html": true }
|
||||
#[serde(default)]
|
||||
pub auto_install_extensions: HashMap<Arc<str>, bool>,
|
||||
#[serde(default)]
|
||||
pub auto_update_extensions: HashMap<Arc<str>, bool>,
|
||||
/// The capabilities granted to extensions.
|
||||
#[serde(default)]
|
||||
pub granted_extension_capabilities: Option<Vec<ExtensionCapabilityContent>>,
|
||||
}
|
||||
|
||||
/// A capability for an extension.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ExtensionCapabilityContent {
|
||||
#[serde(rename = "process:exec")]
|
||||
ProcessExec(ProcessExecCapabilityContent),
|
||||
DownloadFile(DownloadFileCapabilityContent),
|
||||
#[serde(rename = "npm:install")]
|
||||
NpmInstallPackage(NpmInstallPackageCapabilityContent),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ProcessExecCapabilityContent {
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
/// The arguments to pass to the command. Use `*` for a single wildcard argument.
|
||||
/// If the last element is `**`, then any trailing arguments are allowed.
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct DownloadFileCapabilityContent {
|
||||
pub host: String,
|
||||
pub path: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct NpmInstallPackageCapabilityContent {
|
||||
pub package: String,
|
||||
}
|
||||
Reference in New Issue
Block a user