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>
pull/2545/head
Tillmann 2 months ago committed by GitHub
parent 4dd5c51436
commit 9cf0390a52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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",

@ -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.

@ -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`"

@ -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)
}

@ -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
}
}

@ -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) => {

@ -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)
}
}

@ -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

Loading…
Cancel
Save