diff --git a/crates/extension/src/capabilities.rs b/crates/extension/src/capabilities.rs index f88f107991..b8afc4ec06 100644 --- a/crates/extension/src/capabilities.rs +++ b/crates/extension/src/capabilities.rs @@ -1,7 +1,9 @@ mod download_file_capability; +mod npm_install_package_capability; mod process_exec_capability; pub use download_file_capability::*; +pub use npm_install_package_capability::*; pub use process_exec_capability::*; use serde::{Deserialize, Serialize}; @@ -13,4 +15,6 @@ pub enum ExtensionCapability { #[serde(rename = "process:exec")] ProcessExec(ProcessExecCapability), DownloadFile(DownloadFileCapability), + #[serde(rename = "npm:install")] + NpmInstallPackage(NpmInstallPackageCapability), } diff --git a/crates/extension/src/capabilities/npm_install_package_capability.rs b/crates/extension/src/capabilities/npm_install_package_capability.rs new file mode 100644 index 0000000000..287645fc75 --- /dev/null +++ b/crates/extension/src/capabilities/npm_install_package_capability.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct NpmInstallPackageCapability { + pub package: String, +} + +impl NpmInstallPackageCapability { + /// Returns whether the capability allows installing the given NPM package. + pub fn allows(&self, package: &str) -> bool { + self.package == "*" || self.package == package + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_allows() { + let capability = NpmInstallPackageCapability { + package: "*".to_string(), + }; + assert_eq!(capability.allows("package"), true); + + let capability = NpmInstallPackageCapability { + package: "react".to_string(), + }; + assert_eq!(capability.allows("react"), true); + + let capability = NpmInstallPackageCapability { + package: "react".to_string(), + }; + assert_eq!(capability.allows("malicious-package"), false); + } +} diff --git a/crates/extension_host/src/capability_granter.rs b/crates/extension_host/src/capability_granter.rs index 42a6244003..c77e5ecba1 100644 --- a/crates/extension_host/src/capability_granter.rs +++ b/crates/extension_host/src/capability_granter.rs @@ -63,6 +63,24 @@ impl CapabilityGranter { Ok(()) } + + pub fn grant_npm_install_package(&self, package_name: &str) -> Result<()> { + let is_allowed = self + .granted_capabilities + .iter() + .any(|capability| match capability { + ExtensionCapability::NpmInstallPackage(capability) => { + capability.allows(package_name) + } + _ => false, + }); + + if !is_allowed { + bail!("capability for npm:install {package_name} is not granted by the extension host",); + } + + Ok(()) + } } #[cfg(test)] diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 59ecf2ec32..1f6f5035e3 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -8,8 +8,8 @@ use dap::{DebugRequest, StartDebuggingRequestArgumentsRequest}; use extension::{ CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary, DebugTaskDefinition, DownloadFileCapability, ExtensionCapability, ExtensionHostProxy, - KeyValueStoreDelegate, ProcessExecCapability, ProjectDelegate, SlashCommand, - SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, + KeyValueStoreDelegate, NpmInstallPackageCapability, ProcessExecCapability, ProjectDelegate, + SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, }; use fs::{Fs, normalize_path}; use futures::future::LocalBoxFuture; @@ -585,6 +585,9 @@ impl WasmHost { host: "*".to_string(), path: vec!["**".to_string()], }), + ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability { + package: "*".to_string(), + }), ], _main_thread_message_task: task, main_thread_message_tx: tx, diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 7ff28d691f..767b9033ad 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -745,6 +745,9 @@ impl nodejs::Host for WasmState { package_name: String, version: String, ) -> wasmtime::Result> { + self.capability_granter + .grant_npm_install_package(&package_name)?; + self.host .node_runtime .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])