Skip to content

Commit 9c0593c

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 ca45fdb commit 9c0593c

File tree

12 files changed

+141
-59
lines changed

12 files changed

+141
-59
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.2.1", path = "../tauri-utils" }
1919
uuid = { version = "1", features = [ "v4" ] }
2020
rand = "0.8"
2121
raw-window-handle = "0.5"
22+
url = "2"
2223

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

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWind
3737
#[cfg(target_os = "macos")]
3838
use tauri_utils::TitleBarStyle;
3939
use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
40+
use url::Url;
4041
use uuid::Uuid;
4142
use wry::{
4243
application::{
@@ -211,6 +212,7 @@ impl<T: UserEvent> Context<T> {
211212
impl<T: UserEvent> Context<T> {
212213
fn create_webview(&self, pending: PendingWindow<T, Wry<T>>) -> Result<DetachedWindow<T, Wry<T>>> {
213214
let label = pending.label.clone();
215+
let current_url = pending.current_url.clone();
214216
let menu_ids = pending.menu_ids.clone();
215217
let js_event_listeners = pending.js_event_listeners.clone();
216218
let context = self.clone();
@@ -232,6 +234,7 @@ impl<T: UserEvent> Context<T> {
232234
};
233235
Ok(DetachedWindow {
234236
label,
237+
current_url,
235238
dispatcher,
236239
menu_ids,
237240
js_event_listeners,
@@ -1931,6 +1934,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
19311934

19321935
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
19331936
let label = pending.label.clone();
1937+
let current_url = pending.current_url.clone();
19341938
let menu_ids = pending.menu_ids.clone();
19351939
let js_event_listeners = pending.js_event_listeners.clone();
19361940
let window_id = rand::random();
@@ -1957,6 +1961,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
19571961

19581962
Ok(DetachedWindow {
19591963
label,
1964+
current_url,
19601965
dispatcher,
19611966
menu_ids,
19621967
js_event_listeners,
@@ -2944,7 +2949,7 @@ fn create_webview<T: UserEvent>(
29442949
mut window_builder,
29452950
ipc_handler,
29462951
label,
2947-
url,
2952+
current_url,
29482953
menu_ids,
29492954
js_event_listeners,
29502955
..
@@ -2990,7 +2995,7 @@ fn create_webview<T: UserEvent>(
29902995
}
29912996
let mut webview_builder = WebViewBuilder::new(window)
29922997
.map_err(|e| Error::CreateWebview(Box::new(e)))?
2993-
.with_url(&url)
2998+
.with_url(current_url.lock().unwrap().as_str())
29942999
.unwrap() // safe to unwrap because we validate the URL beforehand
29953000
.with_transparent(is_window_transparent)
29963001
.with_accept_first_mouse(webview_attributes.accept_first_mouse);
@@ -3001,10 +3006,16 @@ fn create_webview<T: UserEvent>(
30013006
if let Some(user_agent) = webview_attributes.user_agent {
30023007
webview_builder = webview_builder.with_user_agent(&user_agent);
30033008
}
3009+
if let Some(navigation_handler) = pending.navigation_handler {
3010+
webview_builder = webview_builder.with_navigation_handler(move |url| {
3011+
Url::parse(&url).map(&navigation_handler).unwrap_or(true)
3012+
});
3013+
}
30043014
if let Some(handler) = ipc_handler {
30053015
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
30063016
context,
30073017
label.clone(),
3018+
current_url,
30083019
menu_ids,
30093020
js_event_listeners,
30103021
handler,
@@ -3115,6 +3126,7 @@ fn create_webview<T: UserEvent>(
31153126
fn create_ipc_handler<T: UserEvent>(
31163127
context: Context<T>,
31173128
label: String,
3129+
current_url: Arc<Mutex<Url>>,
31183130
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
31193131
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
31203132
handler: WebviewIpcHandler<T, Wry<T>>,
@@ -3123,6 +3135,7 @@ fn create_ipc_handler<T: UserEvent>(
31233135
let window_id = context.webview_id_map.get(&window.id()).unwrap();
31243136
handler(
31253137
DetachedWindow {
3138+
current_url: current_url.clone(),
31263139
dispatcher: WryDispatcher {
31273140
window_id,
31283141
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
raw-window-handle = "0.5"
3434
rand = "0.8"
35+
url = "2"
3536

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

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},
@@ -224,14 +225,17 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
224225
/// How to handle IPC calls on the webview window.
225226
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
226227

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

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

237241
pub fn is_label_valid(label: &str) -> bool {
@@ -268,9 +272,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
268272
uri_scheme_protocols: Default::default(),
269273
label,
270274
ipc_handler: None,
271-
url: "tauri://localhost".to_string(),
272275
menu_ids: Arc::new(Mutex::new(menu_ids)),
273276
js_event_listeners: Default::default(),
277+
navigation_handler: Default::default(),
278+
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
274279
})
275280
}
276281
}
@@ -297,9 +302,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
297302
uri_scheme_protocols: Default::default(),
298303
label,
299304
ipc_handler: None,
300-
url: "tauri://localhost".to_string(),
301305
menu_ids: Arc::new(Mutex::new(menu_ids)),
302306
js_event_listeners: Default::default(),
307+
navigation_handler: Default::default(),
308+
current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())),
303309
})
304310
}
305311
}
@@ -340,6 +346,9 @@ pub struct JsEventListenerKey {
340346
/// A webview window that is not yet managed by Tauri.
341347
#[derive(Debug)]
342348
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
349+
/// The current webview URL.
350+
pub current_url: Arc<Mutex<Url>>,
351+
343352
/// Name of the window
344353
pub label: String,
345354

@@ -356,6 +365,7 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
356365
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
357366
fn clone(&self) -> Self {
358367
Self {
368+
current_url: self.current_url.clone(),
359369
label: self.label.clone(),
360370
dispatcher: self.dispatcher.clone(),
361371
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
@@ -1017,7 +1017,7 @@ impl<R: Runtime> Builder<R> {
10171017
#[cfg(any(windows, target_os = "linux"))]
10181018
runtime_any_thread: false,
10191019
setup: Box::new(|_| Ok(())),
1020-
invoke_handler: Box::new(|_| ()),
1020+
invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")),
10211021
invoke_responder: Arc::new(window_invoke_responder),
10221022
invoke_initialization_script:
10231023
"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};
@@ -139,7 +139,7 @@ fn set_csp<R: Runtime>(
139139
let default_src = csp
140140
.entry("default-src".into())
141141
.or_insert_with(Default::default);
142-
default_src.push(format_real_schema(schema));
142+
default_src.push(crate::pattern::format_real_schema(schema));
143143
}
144144

145145
Csp::DirectiveMap(csp).to_string()
@@ -231,7 +231,7 @@ pub struct InnerWindowManager<R: Runtime> {
231231
/// The script that initializes the invoke system.
232232
invoke_initialization_script: String,
233233
/// Application pattern.
234-
pattern: Pattern,
234+
pub(crate) pattern: Pattern,
235235
}
236236

237237
impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
@@ -367,9 +367,12 @@ impl<R: Runtime> WindowManager<R> {
367367
/// Get the base URL to use for webview requests.
368368
///
369369
/// In dev mode, this will be based on the `devPath` configuration value.
370-
fn get_url(&self) -> Cow<'_, Url> {
370+
pub(crate) fn get_url(&self) -> Cow<'_, Url> {
371371
match self.base_path() {
372372
AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url),
373+
#[cfg(windows)]
374+
_ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()),
375+
#[cfg(not(windows))]
373376
_ => Cow::Owned(Url::parse("tauri://localhost").unwrap()),
374377
}
375378
}
@@ -477,7 +480,7 @@ impl<R: Runtime> WindowManager<R> {
477480
});
478481
}
479482

480-
let window_url = Url::parse(&pending.url).unwrap();
483+
let window_url = pending.current_url.lock().unwrap().clone();
481484
let window_origin =
482485
if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
483486
format!("https://{}.localhost", window_url.scheme())
@@ -1072,7 +1075,16 @@ mod test {
10721075
);
10731076

10741077
#[cfg(custom_protocol)]
1075-
assert_eq!(manager.get_url().to_string(), "tauri://localhost");
1078+
{
1079+
assert_eq!(
1080+
manager.get_url().to_string(),
1081+
if cfg!(windows) {
1082+
"https://tauri.localhost/"
1083+
} else {
1084+
"tauri://localhost"
1085+
}
1086+
);
1087+
}
10761088

10771089
#[cfg(dev)]
10781090
assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
@@ -1123,27 +1135,21 @@ impl<R: Runtime> WindowManager<R> {
11231135
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
11241136
}
11251137
#[allow(unused_mut)] // mut url only for the data-url parsing
1126-
let (is_local, mut url) = match &pending.webview_attributes.url {
1138+
let mut url = match &pending.webview_attributes.url {
11271139
WindowUrl::App(path) => {
11281140
let url = self.get_url();
1129-
(
1130-
true,
1131-
// ignore "index.html" just to simplify the url
1132-
if path.to_str() != Some("index.html") {
1133-
url
1134-
.join(&path.to_string_lossy())
1135-
.map_err(crate::Error::InvalidUrl)
1136-
// this will never fail
1137-
.unwrap()
1138-
} else {
1139-
url.into_owned()
1140-
},
1141-
)
1142-
}
1143-
WindowUrl::External(url) => {
1144-
let config_url = self.get_url();
1145-
(config_url.make_relative(url).is_some(), url.clone())
1141+
// ignore "index.html" just to simplify the url
1142+
if path.to_str() != Some("index.html") {
1143+
url
1144+
.join(&*path.to_string_lossy())
1145+
.map_err(crate::Error::InvalidUrl)
1146+
// this will never fail
1147+
.unwrap()
1148+
} else {
1149+
url.into_owned()
1150+
}
11461151
}
1152+
WindowUrl::External(url) => url.clone(),
11471153
_ => unimplemented!(),
11481154
};
11491155

@@ -1170,7 +1176,7 @@ impl<R: Runtime> WindowManager<R> {
11701176
}
11711177
}
11721178

1173-
pending.url = url.to_string();
1179+
*pending.current_url.lock().unwrap() = url;
11741180

11751181
if !pending.window_builder.has_icon() {
11761182
if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
@@ -1186,17 +1192,15 @@ impl<R: Runtime> WindowManager<R> {
11861192
}
11871193
}
11881194

1189-
if is_local {
1190-
let label = pending.label.clone();
1191-
pending = self.prepare_pending_window(
1192-
pending,
1193-
&label,
1194-
window_labels,
1195-
app_handle.clone(),
1196-
web_resource_request_handler,
1197-
)?;
1198-
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
1199-
}
1195+
let label = pending.label.clone();
1196+
pending = self.prepare_pending_window(
1197+
pending,
1198+
&label,
1199+
window_labels,
1200+
app_handle.clone(),
1201+
web_resource_request_handler,
1202+
)?;
1203+
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
12001204

12011205
// in `Windows`, we need to force a data_directory
12021206
// but we do respect user-specification
@@ -1221,6 +1225,17 @@ impl<R: Runtime> WindowManager<R> {
12211225
}
12221226
}
12231227

1228+
let current_url_ = pending.current_url.clone();
1229+
let navigation_handler = pending.navigation_handler.take();
1230+
pending.navigation_handler = Some(Box::new(move |url| {
1231+
*current_url_.lock().unwrap() = url.clone();
1232+
if let Some(handler) = &navigation_handler {
1233+
handler(url)
1234+
} else {
1235+
true
1236+
}
1237+
}));
1238+
12241239
Ok(pending)
12251240
}
12261241

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)