Skip to content

Commit 58ea0b4

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 e4159d4 commit 58ea0b4

File tree

12 files changed

+139
-57
lines changed

12 files changed

+139
-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.1.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
@@ -36,6 +36,7 @@ use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
3636
use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
3737

3838
use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
39+
use url::Url;
3940
use uuid::Uuid;
4041
use wry::{
4142
application::{
@@ -210,6 +211,7 @@ impl<T: UserEvent> Context<T> {
210211
impl<T: UserEvent> Context<T> {
211212
fn create_webview(&self, pending: PendingWindow<T, Wry<T>>) -> Result<DetachedWindow<T, Wry<T>>> {
212213
let label = pending.label.clone();
214+
let current_url = pending.current_url.clone();
213215
let menu_ids = pending.menu_ids.clone();
214216
let js_event_listeners = pending.js_event_listeners.clone();
215217
let context = self.clone();
@@ -231,6 +233,7 @@ impl<T: UserEvent> Context<T> {
231233
};
232234
Ok(DetachedWindow {
233235
label,
236+
current_url,
234237
dispatcher,
235238
menu_ids,
236239
js_event_listeners,
@@ -1864,6 +1867,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
18641867

18651868
fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
18661869
let label = pending.label.clone();
1870+
let current_url = pending.current_url.clone();
18671871
let menu_ids = pending.menu_ids.clone();
18681872
let js_event_listeners = pending.js_event_listeners.clone();
18691873
let window_id = rand::random();
@@ -1890,6 +1894,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
18901894

18911895
Ok(DetachedWindow {
18921896
label,
1897+
current_url,
18931898
dispatcher,
18941899
menu_ids,
18951900
js_event_listeners,
@@ -2848,7 +2853,7 @@ fn create_webview<T: UserEvent>(
28482853
mut window_builder,
28492854
ipc_handler,
28502855
label,
2851-
url,
2856+
current_url,
28522857
menu_ids,
28532858
js_event_listeners,
28542859
..
@@ -2888,17 +2893,23 @@ fn create_webview<T: UserEvent>(
28882893
}
28892894
let mut webview_builder = WebViewBuilder::new(window)
28902895
.map_err(|e| Error::CreateWebview(Box::new(e)))?
2891-
.with_url(&url)
2896+
.with_url(current_url.lock().unwrap().as_str())
28922897
.unwrap() // safe to unwrap because we validate the URL beforehand
28932898
.with_transparent(is_window_transparent);
28942899
if webview_attributes.file_drop_handler_enabled {
28952900
webview_builder = webview_builder
28962901
.with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone()));
28972902
}
2903+
if let Some(navigation_handler) = pending.navigation_handler {
2904+
webview_builder = webview_builder.with_navigation_handler(move |url| {
2905+
Url::parse(&url).map(&navigation_handler).unwrap_or(true)
2906+
});
2907+
}
28982908
if let Some(handler) = ipc_handler {
28992909
webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
29002910
context,
29012911
label.clone(),
2912+
current_url,
29022913
menu_ids,
29032914
js_event_listeners,
29042915
handler,
@@ -3002,6 +3013,7 @@ fn create_webview<T: UserEvent>(
30023013
fn create_ipc_handler<T: UserEvent>(
30033014
context: Context<T>,
30043015
label: String,
3016+
current_url: Arc<Mutex<Url>>,
30053017
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
30063018
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
30073019
handler: WebviewIpcHandler<T, Wry<T>>,
@@ -3010,6 +3022,7 @@ fn create_ipc_handler<T: UserEvent>(
30103022
let window_id = context.webview_id_map.get(&window.id()).unwrap();
30113023
handler(
30123024
DetachedWindow {
3025+
current_url: current_url.clone(),
30133026
dispatcher: WryDispatcher {
30143027
window_id,
30153028
context: context.clone(),

core/tauri-runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ http-range = "0.1.4"
3333
infer = "0.7"
3434
raw-window-handle = "0.5"
3535
rand = "0.8"
36+
url = "2"
3637

3738
[target."cfg(windows)".dependencies]
3839
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
@@ -948,7 +948,7 @@ impl<R: Runtime> Builder<R> {
948948
#[cfg(any(windows, target_os = "linux"))]
949949
runtime_any_thread: false,
950950
setup: Box::new(|_| Ok(())),
951-
invoke_handler: Box::new(|_| ()),
951+
invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")),
952952
invoke_responder: Arc::new(window_invoke_responder),
953953
invoke_initialization_script:
954954
"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())
@@ -1011,7 +1014,16 @@ mod test {
10111014
);
10121015

10131016
#[cfg(custom_protocol)]
1014-
assert_eq!(manager.get_url().to_string(), "tauri://localhost");
1017+
{
1018+
assert_eq!(
1019+
manager.get_url().to_string(),
1020+
if cfg!(windows) {
1021+
"https://tauri.localhost/"
1022+
} else {
1023+
"tauri://localhost"
1024+
}
1025+
);
1026+
}
10151027

10161028
#[cfg(dev)]
10171029
assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
@@ -1062,27 +1074,21 @@ impl<R: Runtime> WindowManager<R> {
10621074
return Err(crate::Error::WindowLabelAlreadyExists(pending.label));
10631075
}
10641076
#[allow(unused_mut)] // mut url only for the data-url parsing
1065-
let (is_local, mut url) = match &pending.webview_attributes.url {
1077+
let mut url = match &pending.webview_attributes.url {
10661078
WindowUrl::App(path) => {
10671079
let url = self.get_url();
1068-
(
1069-
true,
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-
},
1080-
)
1081-
}
1082-
WindowUrl::External(url) => {
1083-
let config_url = self.get_url();
1084-
(config_url.make_relative(url).is_some(), url.clone())
1080+
// ignore "index.html" just to simplify the url
1081+
if path.to_str() != Some("index.html") {
1082+
url
1083+
.join(&*path.to_string_lossy())
1084+
.map_err(crate::Error::InvalidUrl)
1085+
// this will never fail
1086+
.unwrap()
1087+
} else {
1088+
url.into_owned()
1089+
}
10851090
}
1091+
WindowUrl::External(url) => url.clone(),
10861092
_ => unimplemented!(),
10871093
};
10881094

@@ -1109,7 +1115,7 @@ impl<R: Runtime> WindowManager<R> {
11091115
}
11101116
}
11111117

1112-
pending.url = url.to_string();
1118+
*pending.current_url.lock().unwrap() = url;
11131119

11141120
if !pending.window_builder.has_icon() {
11151121
if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
@@ -1125,17 +1131,15 @@ impl<R: Runtime> WindowManager<R> {
11251131
}
11261132
}
11271133

1128-
if is_local {
1129-
let label = pending.label.clone();
1130-
pending = self.prepare_pending_window(
1131-
pending,
1132-
&label,
1133-
window_labels,
1134-
app_handle.clone(),
1135-
web_resource_request_handler,
1136-
)?;
1137-
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
1138-
}
1134+
let label = pending.label.clone();
1135+
pending = self.prepare_pending_window(
1136+
pending,
1137+
&label,
1138+
window_labels,
1139+
app_handle.clone(),
1140+
web_resource_request_handler,
1141+
)?;
1142+
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
11391143

11401144
// in `Windows`, we need to force a data_directory
11411145
// but we do respect user-specification
@@ -1160,6 +1164,17 @@ impl<R: Runtime> WindowManager<R> {
11601164
}
11611165
}
11621166

1167+
let current_url_ = pending.current_url.clone();
1168+
let navigation_handler = pending.navigation_handler.take();
1169+
pending.navigation_handler = Some(Box::new(move |url| {
1170+
*current_url_.lock().unwrap() = url.clone();
1171+
if let Some(handler) = &navigation_handler {
1172+
handler(url)
1173+
} else {
1174+
true
1175+
}
1176+
}));
1177+
11631178
Ok(pending)
11641179
}
11651180

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)