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:
Marshall Bowers
2025-10-03 15:55:01 +00:00
committed by GitHub
parent 456ba32ea7
commit 3bf71c690f
6 changed files with 110 additions and 33 deletions
@@ -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(),
}
}
}
+9 -17
View File
@@ -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,
})