Merge commit from fork

* fix(shell): properly validate open scope

* change empty regex to an impossible match

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Co-authored-by: Chip Reed <chip@chip.sh>
This commit is contained in:
Tillmann
2025-04-02 12:24:25 +09:00
committed by GitHub
parent 4dd5c51436
commit 9cf0390a52
11 changed files with 44 additions and 11 deletions
+7
View File
@@ -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`.
@@ -53,7 +53,7 @@
}
]
},
"shell:allow-open",
"shell:default",
"shell:allow-kill",
"shell:allow-stdin-write",
"process:allow-exit",
+2 -1
View File
@@ -35,5 +35,6 @@
},
"engines": {
"pnpm": "^10.0.0"
}
},
"packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6"
}
@@ -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.
+1 -1
View File
@@ -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.
"""
@@ -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`"
+1 -1
View File
@@ -311,5 +311,5 @@ pub async fn open<R: Runtime>(
path: String,
with: Option<Program>,
) -> crate::Result<()> {
shell.open(path, with)
crate::open::open(Some(&shell.open_scope), path, with)
}
+4 -1
View File
@@ -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
}
}
+3 -2
View File
@@ -75,7 +75,7 @@ impl<R: Runtime> Shell<R> {
#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
#[allow(deprecated)]
pub fn open(&self, path: impl Into<String>, with: Option<open::Program>) -> 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<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
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) => {
+16 -2
View File
@@ -119,6 +119,20 @@ impl Program {
/// });
/// ```
#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
pub fn open<P: AsRef<str>>(scope: &OpenScope, path: P, with: Option<Program>) -> crate::Result<()> {
scope.open(path.as_ref(), with).map_err(Into::into)
pub fn open<P: AsRef<str>>(
scope: Option<&OpenScope>,
path: P,
with: Option<Program>,
) -> 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)
}
}
+7
View File
@@ -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<Regex>,
}
@@ -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