From 9cf0390a52497e273db1a1b613a0e26827aa327c Mon Sep 17 00:00:00 2001 From: Tillmann <28728469+tweidinger@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:24:25 +0900 Subject: [PATCH] Merge commit from fork * fix(shell): properly validate open scope * change empty regex to an impossible match --------- Co-authored-by: Lucas Nogueira Co-authored-by: Chip Reed --- .changes/fix-shell-open-scope.md | 7 +++++++ examples/api/src-tauri/capabilities/base.json | 2 +- package.json | 3 ++- .../permissions/autogenerated/reference.md | 2 +- plugins/shell/permissions/default.toml | 2 +- plugins/shell/permissions/schemas/schema.json | 2 +- plugins/shell/src/commands.rs | 2 +- plugins/shell/src/config.rs | 5 ++++- plugins/shell/src/lib.rs | 5 +++-- plugins/shell/src/open.rs | 18 ++++++++++++++++-- plugins/shell/src/scope.rs | 7 +++++++ 11 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 .changes/fix-shell-open-scope.md diff --git a/.changes/fix-shell-open-scope.md b/.changes/fix-shell-open-scope.md new file mode 100644 index 000000000..1b0a1bb8d --- /dev/null +++ b/.changes/fix-shell-open-scope.md @@ -0,0 +1,7 @@ +--- +"shell": patch:bug +"shell-js": patch:bug +--- + +Apply the default open validation regex `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+` when the open configuration is not set, preventing unchecked input from being used in this scenario (previously the plugin would skip validation when it should disable all calls). This keeps backwards compatibility while still fixing this vulnerability. +The scope is no longer validated for Rust calls via `ShellExt::shell()` so if you need to block JavaScript from calling the API you can simply set `tauri.conf.json > plugins > shell > open` to `false`. diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json index 92532e7d1..b8f805a3d 100644 --- a/examples/api/src-tauri/capabilities/base.json +++ b/examples/api/src-tauri/capabilities/base.json @@ -53,7 +53,7 @@ } ] }, - "shell:allow-open", + "shell:default", "shell:allow-kill", "shell:allow-stdin-write", "process:allow-exit", diff --git a/package.json b/package.json index fd6e8775d..b8b8cf161 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,6 @@ }, "engines": { "pnpm": "^10.0.0" - } + }, + "packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6" } diff --git a/plugins/shell/permissions/autogenerated/reference.md b/plugins/shell/permissions/autogenerated/reference.md index 602814f23..d2b86f8a7 100644 --- a/plugins/shell/permissions/autogenerated/reference.md +++ b/plugins/shell/permissions/autogenerated/reference.md @@ -5,7 +5,7 @@ shell functionality is exposed by default. #### Granted Permissions -It allows to use the `open` functionality without any specific +It allows to use the `open` functionality with a reasonable scope pre-configured. It will allow opening `http(s)://`, `tel:` and `mailto:` links. diff --git a/plugins/shell/permissions/default.toml b/plugins/shell/permissions/default.toml index 4569b0528..dba2ea204 100644 --- a/plugins/shell/permissions/default.toml +++ b/plugins/shell/permissions/default.toml @@ -7,7 +7,7 @@ shell functionality is exposed by default. #### Granted Permissions -It allows to use the `open` functionality without any specific +It allows to use the `open` functionality with a reasonable scope pre-configured. It will allow opening `http(s)://`, `tel:` and `mailto:` links. """ diff --git a/plugins/shell/permissions/schemas/schema.json b/plugins/shell/permissions/schemas/schema.json index b729595f0..f8127c57d 100644 --- a/plugins/shell/permissions/schemas/schema.json +++ b/plugins/shell/permissions/schemas/schema.json @@ -355,7 +355,7 @@ "markdownDescription": "Denies the stdin_write command without any pre-configured scope." }, { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", "type": "string", "const": "default", "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" diff --git a/plugins/shell/src/commands.rs b/plugins/shell/src/commands.rs index 5bee6b92e..5275e9b9c 100644 --- a/plugins/shell/src/commands.rs +++ b/plugins/shell/src/commands.rs @@ -311,5 +311,5 @@ pub async fn open( path: String, with: Option, ) -> crate::Result<()> { - shell.open(path, with) + crate::open::open(Some(&shell.open_scope), path, with) } diff --git a/plugins/shell/src/config.rs b/plugins/shell/src/config.rs index cad267a26..69a92ee15 100644 --- a/plugins/shell/src/config.rs +++ b/plugins/shell/src/config.rs @@ -18,6 +18,9 @@ pub struct Config { #[serde(untagged, deny_unknown_fields)] #[non_exhaustive] pub enum ShellAllowlistOpen { + /// Shell open API allowlist is not defined by the user. + /// In this case we add the default validation regex (same as [`Self::Flag(true)`]). + Unset, /// If the shell open API should be enabled. /// /// If enabled, the default validation regex (`^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`) is used. @@ -35,6 +38,6 @@ pub enum ShellAllowlistOpen { impl Default for ShellAllowlistOpen { fn default() -> Self { - Self::Flag(false) + Self::Unset } } diff --git a/plugins/shell/src/lib.rs b/plugins/shell/src/lib.rs index 3dffc50be..c9503731a 100644 --- a/plugins/shell/src/lib.rs +++ b/plugins/shell/src/lib.rs @@ -75,7 +75,7 @@ impl Shell { #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] #[allow(deprecated)] pub fn open(&self, path: impl Into, with: Option) -> Result<()> { - open::open(&self.open_scope, path.into(), with) + open::open(None, path.into(), with) } /// Open a (url) path with a default or specific browser opening program. @@ -147,7 +147,8 @@ pub fn init() -> TauriPlugin> { fn open_scope(open: &config::ShellAllowlistOpen) -> scope::OpenScope { let shell_scope_open = match open { config::ShellAllowlistOpen::Flag(false) => None, - config::ShellAllowlistOpen::Flag(true) => { + // we want to add a basic regex validation even if the config is not set + config::ShellAllowlistOpen::Unset | config::ShellAllowlistOpen::Flag(true) => { Some(Regex::new(r"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+").unwrap()) } config::ShellAllowlistOpen::Validate(validator) => { diff --git a/plugins/shell/src/open.rs b/plugins/shell/src/open.rs index fe2247040..6958a8324 100644 --- a/plugins/shell/src/open.rs +++ b/plugins/shell/src/open.rs @@ -119,6 +119,20 @@ impl Program { /// }); /// ``` #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] -pub fn open>(scope: &OpenScope, path: P, with: Option) -> crate::Result<()> { - scope.open(path.as_ref(), with).map_err(Into::into) +pub fn open>( + scope: Option<&OpenScope>, + path: P, + with: Option, +) -> crate::Result<()> { + // validate scope if we have any (JS calls) + if let Some(scope) = scope { + scope.open(path.as_ref(), with).map_err(Into::into) + } else { + // when running directly from Rust code we don't need to validate the path + match with.map(Program::name) { + Some(program) => ::open::with_detached(path.as_ref(), program), + None => ::open::that_detached(path.as_ref()), + } + .map_err(Into::into) + } } diff --git a/plugins/shell/src/scope.rs b/plugins/shell/src/scope.rs index d46bfa202..3a450afae 100644 --- a/plugins/shell/src/scope.rs +++ b/plugins/shell/src/scope.rs @@ -142,6 +142,7 @@ impl ScopeAllowedArg { /// Scope for the open command pub struct OpenScope { /// The validation regex that `shell > open` paths must match against. + /// When set to `None`, no values are accepted. pub open: Option, } @@ -212,6 +213,12 @@ impl OpenScope { validation: regex.as_str().into(), }); } + } else { + log::warn!("open() command called but the plugin configuration denies calls from JavaScript; set `tauri.conf.json > plugins > shell > open` to true or a validation regex string"); + return Err(Error::Validation { + index: 0, + validation: "tauri^".to_string(), // purposefully impossible regex + }); } // The prevention of argument escaping is handled by the usage of std::process::Command::arg by