diff --git a/.changes/ipc-only-main-frame.md b/.changes/ipc-only-main-frame.md new file mode 100644 index 00000000000..1f575c7248a --- /dev/null +++ b/.changes/ipc-only-main-frame.md @@ -0,0 +1,6 @@ +--- +"tauri": patch:sec +"tauri-runtime-wry": patch:sec +--- + +Only process IPC commands from the main frame. diff --git a/Cargo.lock b/Cargo.lock index 3e5aed09a2a..f959b2d4ef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4036,6 +4036,7 @@ dependencies = [ "encoding_rs", "flate2", "futures-util", + "getrandom 0.2.14", "glib", "glob", "gtk", @@ -5515,9 +5516,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.24.8" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04e72739ee84a218e3dbf8625888eadc874285637003ed21ab96a1bbbb538ec" +checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45" dependencies = [ "base64 0.13.1", "block", diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index e2c7ae06984..ec8367604d2 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ "CHANGELOG.md", "/target" ] readme = "README.md" [dependencies] -wry = { version = "0.24.6", default-features = false, features = [ "file-drop", "protocol" ] } +wry = { version = "0.24.10", default-features = false, features = [ "file-drop", "protocol" ] } tauri-runtime = { version = "0.14.3", path = "../tauri-runtime" } tauri-utils = { version = "1.5.4", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 9139ba9cec8..3f0816ae2ad 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -96,6 +96,7 @@ encoding_rs = "0.8.31" sys-locale = { version = "0.2.3", optional = true } tracing = { version = "0.1", optional = true } indexmap = { version = "1", features = [ "std", "serde" ], optional = true } +getrandom = { version = "0.2", features = [ "std" ] } [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] rfd = { version = "0.10", optional = true, features = [ "gtk3", "common-controls-v6" ] } diff --git a/core/tauri/scripts/init.js b/core/tauri/scripts/init.js index a45abd4a1cb..e95c8c2827d 100644 --- a/core/tauri/scripts/init.js +++ b/core/tauri/scripts/init.js @@ -2,19 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -; (function () { +;(function() { __RAW_freeze_prototype__ - ; (function () { - __RAW_hotkeys__ - })() + ;(function() { + __RAW_hotkeys__ + })() __RAW_pattern_script__ __RAW_ipc_script__ - ; (function () { - __RAW_bundle_script__ - })() + ;(function() { + __RAW_bundle_script__ + })() __RAW_listen_function__ @@ -22,11 +22,5 @@ __RAW_event_initialization_script__ - if (window.ipc) { - window.__TAURI_INVOKE__('__initialized', { url: window.location.href }) - } else { - window.addEventListener('DOMContentLoaded', function () { - window.__TAURI_INVOKE__('__initialized', { url: window.location.href }) - }) - } + window.__TAURI_INVOKE__('__initialized', { url: window.location.href }) })() diff --git a/core/tauri/scripts/ipc.js b/core/tauri/scripts/ipc.js index 83ba1121d28..9f37ce34eb7 100644 --- a/core/tauri/scripts/ipc.js +++ b/core/tauri/scripts/ipc.js @@ -7,7 +7,7 @@ */ ; -(function () { +(function() { /** * @type {string} */ @@ -18,6 +18,15 @@ */ const isolationOrigin = __TEMPLATE_isolation_origin__ + /** + * A runtime generated key to ensure an IPC call comes from an initialized frame. + * + * This is declared outside the `window.__TAURI_INVOKE__` definition to prevent + * the key from being leaked by `window.__TAURI_INVOKE__.toString()`. + * @var {string} __TEMPLATE_invoke_key__ + */ + const __TAURI_INVOKE_KEY__ = __TEMPLATE_invoke_key__ + /** * @type {{queue: object[], ready: boolean, frame: HTMLElement | null}} */ @@ -85,6 +94,7 @@ Object.defineProperty(window, '__TAURI_IPC__', { // todo: JSDoc this function value: Object.freeze((message) => { + message.__TAURI_INVOKE_KEY__ = __TAURI_INVOKE_KEY__ switch (pattern) { case 'brownfield': window.__TAURI_POST_MESSAGE__(message) diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 93e47036493..4283d8ac43c 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -1569,6 +1569,7 @@ impl Builder { self.window_event_listeners, (self.menu, self.menu_event_listeners), (self.invoke_responder, self.invoke_initialization_script), + crate::generate_invoke_key()?, ); let http_scheme = manager.config().tauri.security.dangerous_use_http_scheme; diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index f9c1b3e1942..fd32635068a 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -131,6 +131,12 @@ pub enum Error { /// The Window's raw handle is invalid for the platform. #[error("Unexpected `raw_window_handle` for the current platform")] InvalidWindowHandle, + /// Something went wrong with the CSPRNG. + #[error("unable to generate random bytes from the operating system: {0}")] + Csprng(#[from] getrandom::Error), + /// Bad `__TAURI_INVOKE_KEY__` value received in ipc message. + #[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")] + InvokeKey, } pub(crate) fn into_anyhow(err: T) -> anyhow::Error { diff --git a/core/tauri/src/hooks.rs b/core/tauri/src/hooks.rs index e4acd724bd3..30cec21ac31 100644 --- a/core/tauri/src/hooks.rs +++ b/core/tauri/src/hooks.rs @@ -35,6 +35,7 @@ pub type OnPageLoad = dyn Fn(Window, PageLoadPayload) + Send + Sync + 'sta #[default_template("../scripts/ipc.js")] pub(crate) struct IpcJavascript<'a> { pub(crate) isolation_origin: &'a str, + pub(crate) invoke_key: &'a str, } #[cfg(feature = "isolation")] @@ -66,6 +67,10 @@ pub struct InvokePayload { #[serde(rename = "__tauriModule")] #[doc(hidden)] pub tauri_module: Option, + /// A secret key that only Tauri initialized frames have. + #[serde(rename = "__TAURI_INVOKE_KEY__")] + #[doc(hidden)] + pub invoke_key: Option, /// The success callback. pub callback: CallbackFn, /// The error callback. diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 9f4c6521b66..484b81d45ca 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -1091,3 +1091,52 @@ mod test_utils { } } } + +/// Simple dependency-free string encoder using [Z85]. +mod z85 { + const TABLE: &[u8; 85] = + b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"; + + /// Encode bytes with [Z85]. + /// + /// # Panics + /// + /// Will panic if the input bytes are not a multiple of 4. + pub fn encode(bytes: &[u8]) -> String { + assert_eq!(bytes.len() % 4, 0); + + let mut buf = String::with_capacity(bytes.len() * 5 / 4); + for chunk in bytes.chunks_exact(4) { + let mut chars = [0u8; 5]; + let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize; + for byte in chars.iter_mut().rev() { + *byte = TABLE[chunk % 85]; + chunk /= 85; + } + + buf.push_str(std::str::from_utf8(&chars).unwrap()); + } + + buf + } + + #[cfg(test)] + mod tests { + #[test] + fn encode() { + assert_eq!( + super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]), + "HelloWorld" + ); + } + } +} + +/// Generate a random 128-bit [Z85] encoded [`String`]. +/// +/// [Z85]: https://rfc.zeromq.org/spec/32/ +pub(crate) fn generate_invoke_key() -> Result { + let mut bytes = [0u8; 16]; + getrandom::getrandom(&mut bytes)?; + Ok(z85::encode(&bytes)) +} diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 9050f8b33c3..b8dc6a1e5cd 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -238,6 +238,8 @@ pub struct InnerWindowManager { invoke_initialization_script: String, /// Application pattern. pub(crate) pattern: Pattern, + /// A runtime generated key to ensure an IPC call comes from an initialized frame. + invoke_key: String, } impl fmt::Debug for InnerWindowManager { @@ -252,6 +254,7 @@ impl fmt::Debug for InnerWindowManager { .field("package_info", &self.package_info) .field("menu", &self.menu) .field("pattern", &self.pattern) + .field("invoke_key", &self.invoke_key) .finish() } } @@ -303,6 +306,7 @@ impl WindowManager { window_event_listeners: Vec>, (menu, menu_event_listeners): (Option, Vec>), (invoke_responder, invoke_initialization_script): (Arc>, String), + invoke_key: String, ) -> Self { // generate a random isolation key at runtime #[cfg(feature = "isolation")] @@ -333,6 +337,7 @@ impl WindowManager { window_event_listeners: Arc::new(window_event_listeners), invoke_responder, invoke_initialization_script, + invoke_key, }), } } @@ -449,6 +454,7 @@ impl WindowManager { } _ => "".to_string(), }, + invoke_key: self.invoke_key(), } .render_default(&Default::default())?; @@ -896,6 +902,10 @@ impl WindowManager { listeners = self.event_listeners_object_name() ) } + + pub(crate) fn invoke_key(&self) -> &str { + &self.inner.invoke_key + } } #[cfg(test)] @@ -917,6 +927,7 @@ mod test { Default::default(), Default::default(), (std::sync::Arc::new(|_, _, _, _| ()), "".into()), + crate::generate_invoke_key().unwrap(), ); #[cfg(custom_protocol)] diff --git a/core/tauri/src/scope/ipc.rs b/core/tauri/src/scope/ipc.rs index c127fce98e0..eb087da90fa 100644 --- a/core/tauri/src/scope/ipc.rs +++ b/core/tauri/src/scope/ipc.rs @@ -171,6 +171,7 @@ impl Scope { #[cfg(test)] mod tests { use super::RemoteDomainAccessScope; + use crate::sealed::ManagerBase; use crate::{ api::ipc::CallbackFn, test::{assert_ipc_response, mock_app, MockRuntime}, @@ -190,7 +191,7 @@ mod tests { (app, window) } - fn app_version_payload() -> InvokePayload { + fn app_version_payload(invoke_key: &str) -> InvokePayload { let callback = CallbackFn(0); let error = CallbackFn(1); @@ -208,10 +209,11 @@ mod tests { callback, error, inner: serde_json::Value::Object(payload), + invoke_key: Some(invoke_key.into()), } } - fn plugin_test_payload() -> InvokePayload { + fn plugin_test_payload(invoke_key: &str) -> InvokePayload { let callback = CallbackFn(0); let error = CallbackFn(1); @@ -221,19 +223,25 @@ mod tests { callback, error, inner: Default::default(), + invoke_key: Some(invoke_key.into()), } } + fn invoke_key(app: &App) -> &str { + app.manager().invoke_key() + } + #[test] fn scope_not_defined() { - let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app") + let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app") .add_window("other") .enable_tauri_api()]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(&crate::window::ipc_scope_not_found_error_message( "main", "https://tauri.app/", @@ -243,28 +251,30 @@ mod tests { #[test] fn scope_not_defined_for_window() { - let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") .add_window("second") .enable_tauri_api()]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(&crate::window::ipc_scope_window_error_message("main")), ); } #[test] fn scope_not_defined_for_url() { - let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com") + let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com") .add_window("main") .enable_tauri_api()]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(&crate::window::ipc_scope_domain_error_message( "https://tauri.app/", )), @@ -281,18 +291,19 @@ mod tests { .add_window("main") .enable_tauri_api(), ]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Ok(app.package_info().version.to_string().as_str()), ); window.navigate("https://blog.tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(&crate::window::ipc_scope_domain_error_message( "https://blog.tauri.app/", )), @@ -301,7 +312,7 @@ mod tests { window.navigate("https://sub.tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Ok(app.package_info().version.to_string().as_str()), ); @@ -309,7 +320,7 @@ mod tests { window.navigate("https://dev.tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(&crate::window::ipc_scope_not_found_error_message( "test", "https://dev.tauri.app/", @@ -322,53 +333,57 @@ mod tests { let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") .add_window("main") .enable_tauri_api()]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app/inner/path".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Ok(app.package_info().version.to_string().as_str()), ); } #[test] fn tauri_api_not_allowed() { - let (_app, mut window) = test_context(vec![ + let (app, mut window) = test_context(vec![ RemoteDomainAccessScope::new("tauri.app").add_window("main") ]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - app_version_payload(), + app_version_payload(invoke_key), Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW), ); } #[test] fn plugin_allowed() { - let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") + let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app") .add_window("main") .add_plugin(PLUGIN_NAME)]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - plugin_test_payload(), + plugin_test_payload(invoke_key), Err(&format!("plugin {PLUGIN_NAME} not found")), ); } #[test] fn plugin_not_allowed() { - let (_app, mut window) = test_context(vec![ + let (app, mut window) = test_context(vec![ RemoteDomainAccessScope::new("tauri.app").add_window("main") ]); + let invoke_key = invoke_key(&app); window.navigate("https://tauri.app".parse().unwrap()); assert_ipc_response( &window, - plugin_test_payload(), + plugin_test_payload(invoke_key), Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW), ); } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 4307f1bb790..87888b93eb6 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -31,8 +31,8 @@ use crate::{ sealed::ManagerBase, sealed::RuntimeOrDispatch, utils::config::{WindowConfig, WindowUrl}, - CursorIcon, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, - PageLoadPayload, Runtime, Theme, WindowEvent, + CursorIcon, Error, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, + Manager, Runtime, Theme, WindowEvent, }; use serde::Serialize; @@ -1550,9 +1550,35 @@ impl Window { self.current_url = url; } + #[cfg_attr(feature = "tracing", tracing::instrument("window::on_message"))] /// Handles this window receiving an [`InvokeMessage`]. pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> { let manager = self.manager.clone(); + + // ensure the passed key matches what our manager should have injected + let expected = manager.invoke_key(); + match payload.invoke_key.as_deref() { + Some(sent) if sent == expected => { /* good */ } + Some(sent) => { + #[cfg(feature = "tracing")] + tracing::error!("__TAURI_INVOKE_KEY__ expected {expected} but received {sent}"); + + #[cfg(not(feature = "tracing"))] + eprintln!("__TAURI_INVOKE_KEY__ expected {expected} but received {sent}"); + + return Err(Error::InvokeKey); + } + None => { + #[cfg(feature = "tracing")] + tracing::error!("received ipc message without a __TAURI_INVOKE_KEY__"); + + #[cfg(not(feature = "tracing"))] + eprintln!("received ipc message without a __TAURI_INVOKE_KEY__"); + + return Err(Error::InvokeKey); + } + } + let current_url = self.url(); let config_url = manager.get_url(); let is_local = config_url.make_relative(¤t_url).is_some(); @@ -1574,54 +1600,53 @@ impl Window { } } }; - match payload.cmd.as_str() { - "__initialized" => { - let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; - manager.run_on_page_load(self, payload); + + if "__initialized" == &payload.cmd { + let payload = serde_json::from_value(payload.inner)?; + manager.run_on_page_load(self, payload); + return Ok(()); + } + + let message = InvokeMessage::new( + self.clone(), + manager.state(), + payload.cmd.to_string(), + payload.inner, + ); + let resolver = InvokeResolver::new(self, payload.callback, payload.error); + let invoke = Invoke { message, resolver }; + + if !is_local && scope.is_none() { + invoke.resolver.reject(scope_not_found_error_message); + return Ok(()); + } + + if let Some(module) = &payload.tauri_module { + if !is_local && scope.map(|s| !s.enables_tauri_api()).unwrap_or_default() { + invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); + return Ok(()); } - _ => { - let message = InvokeMessage::new( - self.clone(), - manager.state(), - payload.cmd.to_string(), - payload.inner, - ); - let resolver = InvokeResolver::new(self, payload.callback, payload.error); - let invoke = Invoke { message, resolver }; - - if !is_local && scope.is_none() { - invoke.resolver.reject(scope_not_found_error_message); + crate::endpoints::handle( + module.to_string(), + invoke, + manager.config(), + manager.package_info(), + ); + } else if payload.cmd.starts_with("plugin:") { + if !is_local { + let command = invoke.message.command.replace("plugin:", ""); + let plugin_name = command.split('|').next().unwrap().to_string(); + if !scope + .map(|s| s.plugins().contains(&plugin_name)) + .unwrap_or(true) + { + invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); return Ok(()); } - - if let Some(module) = &payload.tauri_module { - if !is_local && scope.map(|s| !s.enables_tauri_api()).unwrap_or_default() { - invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); - return Ok(()); - } - crate::endpoints::handle( - module.to_string(), - invoke, - manager.config(), - manager.package_info(), - ); - } else if payload.cmd.starts_with("plugin:") { - if !is_local { - let command = invoke.message.command.replace("plugin:", ""); - let plugin_name = command.split('|').next().unwrap().to_string(); - if !scope - .map(|s| s.plugins().contains(&plugin_name)) - .unwrap_or(true) - { - invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); - return Ok(()); - } - } - manager.extend_api(invoke); - } else { - manager.run_invoke_handler(invoke); - } } + manager.extend_api(invoke); + } else { + manager.run_invoke_handler(invoke); } Ok(()) diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 39188c0c7aa..e649a2d7697 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -1806,6 +1806,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -4018,7 +4024,7 @@ checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "tauri" -version = "1.6.0" +version = "1.6.6" dependencies = [ "anyhow", "base64 0.21.7", @@ -4031,10 +4037,11 @@ dependencies = [ "encoding_rs", "flate2", "futures-util", + "getrandom 0.2.12", "glib", "glob", "gtk", - "heck 0.4.1", + "heck 0.5.0", "http", "ico 0.2.0", "ignore", @@ -4083,12 +4090,12 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.5.1" +version = "1.5.2" dependencies = [ "anyhow", "cargo_toml", "dirs-next", - "heck 0.4.1", + "heck 0.5.0", "json-patch", "quote", "semver", @@ -4102,7 +4109,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.4.2" +version = "1.4.3" dependencies = [ "base64 0.21.7", "brotli", @@ -4126,9 +4133,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "1.4.3" +version = "1.4.4" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "syn 1.0.109", @@ -4138,7 +4145,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.14.2" +version = "0.14.3" dependencies = [ "gtk", "http", @@ -4157,7 +4164,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.14.4" +version = "0.14.7" dependencies = [ "arboard", "cocoa 0.24.1", @@ -4176,7 +4183,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.5.3" +version = "1.5.4" dependencies = [ "aes-gcm", "brotli", @@ -4184,7 +4191,7 @@ dependencies = [ "dunce", "getrandom 0.2.12", "glob", - "heck 0.4.1", + "heck 0.5.0", "html5ever", "infer 0.13.0", "json-patch", @@ -5494,9 +5501,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.24.7" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4" +checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45" dependencies = [ "base64 0.13.1", "block",