Skip to content

Commit fa90214

Browse files
committed
feat(core): block remote URLs from accessing the IPC
This was cherry picked from ee71c31, keeping only the logic to block remote URLs from using the IPC. PR: #5918
1 parent 34e03b8 commit fa90214

File tree

12 files changed

+128
-57
lines changed

12 files changed

+128
-57
lines changed

.changes/remote-urls.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
"tauri-runtime-wry": patch
5+
---
6+
7+
Block remote URLs from accessing the IPC.

core/tauri-build/src/static_vcruntime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ fn override_msvcrt_lib() {
5454
f.write_all(bytes).unwrap();
5555
}
5656
// Add the output directory to the native library path.
57-
println!("cargo:rustc-link-search=native={}", out_dir);
57+
println!("cargo:rustc-link-search=native={out_dir}");
5858
}

core/tauri-runtime-wry/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ tauri-utils = { version = "1.0.3", path = "../tauri-utils" }
1919
uuid = { version = "1", features = [ "v4" ] }
2020
rand = "0.8"
2121
raw-window-handle = "0.4.3"
22+
url = "2"
2223

2324
[target."cfg(windows)".dependencies]
2425
webview2-com = "0.16.0"

core/tauri-runtime-wry/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWind
4141
use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder};
4242

4343
use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
44+
use url::Url;
4445
use uuid::Uuid;
4546
use wry::{
4647
application::{
@@ -216,6 +217,7 @@ impl<T: UserEvent> Context<T> {
216217
impl<T: UserEvent> Context<T> {
217218
fn create_webview(&self, pending: PendingWindow<T, Wry<T>>) -> Result<DetachedWindow<T, Wry<T>>> {
218219
let label = pending.label.clone();
220+
let current_url = pending.current_url.clone();
219221
let menu_ids = pending.menu_ids.clone();
220222
let js_event_listeners = pending.js_event_listeners.clone();
221223
let context = self.clone();
@@ -237,6 +239,7 @@ impl<T: UserEvent> Context<T> {
237239
};
238240
Ok(DetachedWindow {
239241
label,
242+
current_url,
240243
dispatcher,
241244
menu_ids,
242245
js_event_listeners,
@@ -1862,6 +1865,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
18621865

18631866
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
18641867
let label = pending.label.clone();
1868+
let current_url = pending.current_url.clone();
18651869
let menu_ids = pending.menu_ids.clone();
18661870
let js_event_listeners = pending.js_event_listeners.clone();
18671871
let window_id = rand::random();
@@ -1889,6 +1893,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
18891893

18901894
Ok(DetachedWindow {
18911895
label,
1896+
current_url,
18921897
dispatcher,
18931898
menu_ids,
18941899
js_event_listeners,
@@ -2833,7 +2838,7 @@ fn create_webview<T: UserEvent>(
28332838
mut window_builder,
28342839
ipc_handler,
28352840
label,
2836-
url,
2841+
current_url,
28372842
menu_ids,
28382843
js_event_listeners,
28392844
..
@@ -2871,16 +2876,22 @@ fn create_webview<T: UserEvent>(
28712876
}
28722877
let mut webview_builder = WebViewBuilder::new(window)
28732878
.map_err(|e| Error::CreateWebview(Box::new(e)))?
2874-
.with_url(&url)
2879+
.with_url(current_url.lock().unwrap().as_str())
28752880
.unwrap() // safe to unwrap because we validate the URL beforehand
28762881
.with_transparent(is_window_transparent);
28772882
if webview_attributes.file_drop_handler_enabled {
28782883
webview_builder = webview_builder.with_file_drop_handler(create_file_drop_handler(&context));
28792884
}
2885+
if let Some(navigation_handler) = pending.navigation_handler {
2886+
webview_builder = webview_builder.with_navigation_handler(move |url| {
2887+
Url::parse(&url).map(&navigation_handler).unwrap_or(true)
2888+
});
2889+
}
28802890
if let Some(handler) = ipc_handler {
28812891
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
28822892
context,
28832893
label.clone(),
2894+
current_url,
28842895
menu_ids,
28852896
js_event_listeners,
28862897
handler,
@@ -2984,6 +2995,7 @@ fn create_webview<T: UserEvent>(
29842995
fn create_ipc_handler<T: UserEvent>(
29852996
context: Context<T>,
29862997
label: String,
2998+
current_url: Arc<Mutex<Url>>,
29872999
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
29883000
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
29893001
handler: WebviewIpcHandler<T, Wry<T>>,
@@ -2992,6 +3004,7 @@ fn create_ipc_handler<T: UserEvent>(
29923004
let window_id = context.webview_id_map.get(&window.id());
29933005
handler(
29943006
DetachedWindow {
3007+
current_url: current_url.clone(),
29953008
dispatcher: WryDispatcher {
29963009
window_id,
29973010
context: context.clone(),

core/tauri-runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ http = "0.2.4"
3232
http-range = "0.1.4"
3333
infer = "0.7"
3434
raw-window-handle = "0.4.3"
35+
url = "2"
3536

3637
[target."cfg(windows)".dependencies]
3738
webview2-com = "0.16.0"

core/tauri-runtime/src/window.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
};
1313
use serde::{Deserialize, Deserializer, Serialize};
1414
use tauri_utils::{config::WindowConfig, Theme};
15+
use url::Url;
1516

1617
use std::{
1718
collections::{HashMap, HashSet},
@@ -226,14 +227,17 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
226227
/// How to handle IPC calls on the webview window.
227228
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
228229

229-
/// The resolved URL to load on the webview.
230-
pub url: String,
231-
232230
/// Maps runtime id to a string menu id.
233231
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
234232

235233
/// A HashMap mapping JS event names with associated listener ids.
236234
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
235+
236+
/// A handler to decide if incoming url is allowed to navigate.
237+
pub navigation_handler: Option<Box<dyn Fn(Url) -> bool + Send>>,
238+
239+
/// The current webview URL.
240+
pub current_url: Arc<Mutex<Url>>,
237241
}
238242

239243
pub fn is_label_valid(label: &str) -> bool {
@@ -270,9 +274,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
270274
uri_scheme_protocols: Default::default(),
271275
label,
272276
ipc_handler: None,
273-
url: "tauri://localhost".to_string(),
274277
menu_ids: Arc::new(Mutex::new(menu_ids)),
275278
js_event_listeners: Default::default(),
279+
navigation_handler: Default::default(),
280+
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
276281
})
277282
}
278283
}
@@ -299,9 +304,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
299304
uri_scheme_protocols: Default::default(),
300305
label,
301306
ipc_handler: None,
302-
url: "tauri://localhost".to_string(),
303307
menu_ids: Arc::new(Mutex::new(menu_ids)),
304308
js_event_listeners: Default::default(),
309+
navigation_handler: Default::default(),
310+
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
305311
})
306312
}
307313
}
@@ -342,6 +348,9 @@ pub struct JsEventListenerKey {
342348
/// A webview window that is not yet managed by Tauri.
343349
#[derive(Debug)]
344350
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
351+
/// The current webview URL.
352+
pub current_url: Arc<Mutex<Url>>,
353+
345354
/// Name of the window
346355
pub label: String,
347356

@@ -358,6 +367,7 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
358367
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
359368
fn clone(&self) -> Self {
360369
Self {
370+
current_url: self.current_url.clone(),
361371
label: self.label.clone(),
362372
dispatcher: self.dispatcher.clone(),
363373
menu_ids: self.menu_ids.clone(),

core/tauri/src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ impl<R: Runtime> Builder<R> {
902902
#[cfg(any(windows, target_os = "linux"))]
903903
runtime_any_thread: false,
904904
setup: Box::new(|_| Ok(())),
905-
invoke_handler: Box::new(|_| ()),
905+
invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")),
906906
invoke_responder: Arc::new(window_invoke_responder),
907907
invoke_initialization_script:
908908
"Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(),

core/tauri/src/manager.rs

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ use tauri_utils::{
2525
html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
2626
};
2727

28-
use crate::hooks::IpcJavascript;
2928
#[cfg(feature = "isolation")]
3029
use crate::hooks::IsolationJavascript;
31-
use crate::pattern::{format_real_schema, PatternJavascript};
30+
use crate::pattern::PatternJavascript;
3231
use crate::{
3332
app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
3433
event::{assert_event_name_is_valid, Event, EventHandler, Listeners},
@@ -54,6 +53,7 @@ use crate::{
5453
app::{GlobalMenuEventListener, WindowMenuEvent},
5554
window::WebResourceRequestHandler,
5655
};
56+
use crate::{hooks::IpcJavascript, pattern::format_real_schema};
5757

5858
#[cfg(any(target_os = "linux", target_os = "windows"))]
5959
use crate::api::path::{resolve_path, BaseDirectory};
@@ -136,7 +136,7 @@ fn set_csp<R: Runtime>(
136136
let default_src = csp
137137
.entry("default-src".into())
138138
.or_insert_with(Default::default);
139-
default_src.push(format_real_schema(schema));
139+
default_src.push(crate::pattern::format_real_schema(schema));
140140
}
141141

142142
Csp::DirectiveMap(csp).to_string()
@@ -225,7 +225,7 @@ pub struct InnerWindowManager<R: Runtime> {
225225
/// The script that initializes the invoke system.
226226
invoke_initialization_script: String,
227227
/// Application pattern.
228-
pattern: Pattern,
228+
pub(crate) pattern: Pattern,
229229
}
230230

231231
impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
@@ -357,9 +357,12 @@ impl<R: Runtime> WindowManager<R> {
357357
/// Get the base URL to use for webview requests.
358358
///
359359
/// In dev mode, this will be based on the `devPath` configuration value.
360-
fn get_url(&self) -> Cow<'_, Url> {
360+
pub(crate) fn get_url(&self) -> Cow<'_, Url> {
361361
match self.base_path() {
362362
AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url),
363+
#[cfg(windows)]
364+
_ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()),
365+
#[cfg(not(windows))]
363366
_ => Cow::Owned(Url::parse("tauri://localhost").unwrap()),
364367
}
365368
}
@@ -467,7 +470,7 @@ impl<R: Runtime> WindowManager<R> {
467470
});
468471
}
469472

470-
let window_url = Url::parse(&pending.url).unwrap();
473+
let window_url = pending.current_url.lock().unwrap().clone();
471474
let window_origin =
472475
if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
473476
format!("https://{}.localhost", window_url.scheme())
@@ -1001,7 +1004,16 @@ mod test {
10011004
);
10021005

10031006
#[cfg(custom_protocol)]
1004-
assert_eq!(manager.get_url().to_string(), "tauri://localhost");
1007+
{
1008+
assert_eq!(
1009+
manager.get_url().to_string(),
1010+
if cfg!(windows) {
1011+
"https://tauri.localhost/"
1012+
} else {
1013+
"tauri://localhost"
1014+
}
1015+
);
1016+
}
10051017

10061018
#[cfg(dev)]
10071019
assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
@@ -1052,27 +1064,21 @@ impl<R: Runtime> WindowManager<R> {
10521064
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
10531065
}
10541066
#[allow(unused_mut)] // mut url only for the data-url parsing
1055-
let (is_local, mut url) = match &pending.webview_attributes.url {
1067+
let mut url = match &pending.webview_attributes.url {
10561068
WindowUrl::App(path) => {
10571069
let url = self.get_url();
1058-
(
1059-
true,
1060-
// ignore "index.html" just to simplify the url
1061-
if path.to_str() != Some("index.html") {
1062-
url
1063-
.join(&*path.to_string_lossy())
1064-
.map_err(crate::Error::InvalidUrl)
1065-
// this will never fail
1066-
.unwrap()
1067-
} else {
1068-
url.into_owned()
1069-
},
1070-
)
1071-
}
1072-
WindowUrl::External(url) => {
1073-
let config_url = self.get_url();
1074-
(config_url.make_relative(url).is_some(), url.clone())
1070+
// ignore "index.html" just to simplify the url
1071+
if path.to_str() != Some("index.html") {
1072+
url
1073+
.join(&*path.to_string_lossy())
1074+
.map_err(crate::Error::InvalidUrl)
1075+
// this will never fail
1076+
.unwrap()
1077+
} else {
1078+
url.into_owned()
1079+
}
10751080
}
1081+
WindowUrl::External(url) => url.clone(),
10761082
_ => unimplemented!(),
10771083
};
10781084

@@ -1099,7 +1105,7 @@ impl<R: Runtime> WindowManager<R> {
10991105
}
11001106
}
11011107

1102-
pending.url = url.to_string();
1108+
*pending.current_url.lock().unwrap() = url;
11031109

11041110
if !pending.window_builder.has_icon() {
11051111
if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
@@ -1115,17 +1121,15 @@ impl<R: Runtime> WindowManager<R> {
11151121
}
11161122
}
11171123

1118-
if is_local {
1119-
let label = pending.label.clone();
1120-
pending = self.prepare_pending_window(
1121-
pending,
1122-
&label,
1123-
window_labels,
1124-
app_handle.clone(),
1125-
web_resource_request_handler,
1126-
)?;
1127-
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
1128-
}
1124+
let label = pending.label.clone();
1125+
pending = self.prepare_pending_window(
1126+
pending,
1127+
&label,
1128+
window_labels,
1129+
app_handle.clone(),
1130+
web_resource_request_handler,
1131+
)?;
1132+
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
11291133

11301134
// in `Windows`, we need to force a data_directory
11311135
// but we do respect user-specification
@@ -1150,6 +1154,17 @@ impl<R: Runtime> WindowManager<R> {
11501154
}
11511155
}
11521156

1157+
let current_url_ = pending.current_url.clone();
1158+
let navigation_handler = pending.navigation_handler.take();
1159+
pending.navigation_handler = Some(Box::new(move |url| {
1160+
*current_url_.lock().unwrap() = url.clone();
1161+
if let Some(handler) = &navigation_handler {
1162+
handler(url)
1163+
} else {
1164+
true
1165+
}
1166+
}));
1167+
11531168
Ok(pending)
11541169
}
11551170

core/tauri/src/pattern.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use serialize_to_javascript::{default_template, Template};
1111

1212
use tauri_utils::assets::{Assets, EmbeddedAssets};
1313

14+
/// The domain of the isolation iframe source.
15+
pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost";
16+
1417
/// An application pattern.
1518
#[derive(Debug, Clone)]
1619
pub enum Pattern<A: Assets = EmbeddedAssets> {
@@ -87,8 +90,8 @@ pub(crate) struct PatternJavascript {
8790
#[allow(dead_code)]
8891
pub(crate) fn format_real_schema(schema: &str) -> String {
8992
if cfg!(windows) {
90-
format!("https://{}.localhost", schema)
93+
format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}")
9194
} else {
92-
format!("{}://localhost", schema)
95+
format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}")
9396
}
9497
}

0 commit comments

Comments
 (0)