Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## 2026-04-15 - Prevent HTML Injection in Media Captions\n**Vulnerability:** XSS / HTML Injection in media captions. The `formatted_caption()` was used indiscriminately, injecting `fb.body` into `show_html` regardless of the `fb.format` type. If a malicious client sent a custom format or plaintext with HTML tags, it would be executed by the UI.\n**Learning:** The `FormattedBody` structure from Matrix (via Ruma) must have its `.format` field explicitly checked (e.g., `MessageFormat::Html`) before treating its `.body` as safe HTML, as native UI renders rely on this explicit contract.\n**Prevention:** Always use `.filter(|fb| fb.format == MessageFormat::Html)` when extracting HTML from a `FormattedBody`, and strictly fallback to `htmlize::escape_text` for plain text representations.
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,14 @@ and the Project Robius app dev framework and platform abstractions
Robrix runs on all major desktop and mobile platforms:
macOS, Windows, Linux, Android, and iOS.
"""
icons = ["./packaging/robrix_logo_alpha.png"]
icons = [
"./packaging/robrix_logo_alpha_32x32.png",
"./packaging/robrix_logo_alpha_48x48.png",
"./packaging/robrix_logo_alpha_64x64.png",
"./packaging/robrix_logo_alpha_128x128.png",
"./packaging/robrix_logo_alpha_256x256.png",
"./packaging/robrix_logo_alpha_512x512.png",
]
out_dir = "./dist"

## Here, we define the list of resource directories for both Makepad and Robrix.
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ The following table shows which host systems can currently be used to build Robr
cargo run --release
```

> [!TIP]
> If you get a build error from `aws-lc-sys` about a **"COMPILER BUG DETECTED"** related to `memcmp`
> ([GCC #95189](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189)),
> your GCC version is too old (GCC 9 and earlier are affected).
> The easiest fix is to build using `clang` instead:
> ```sh
> CC=clang CXX=clang++ cargo run --release
> ```
> Alternatively, upgrade GCC to version 10 or newer.


## Building & Running Robrix on Mobile: Android, iOS, iPadOS

1. Install the `cargo-makepad` build tool:
Expand Down Expand Up @@ -122,6 +133,12 @@ The following table shows which host systems can currently be used to build Robr
--app=robrix \
run-sim -p robrix --release
```
> [!TIP]
> If you get errors from the simulator, update your simulator tooling:
> ```sh
> xcodebuild -downloadPlatform iOS
> ```


#### Running on a real iOS device
4. Run the following command to show all provisioning profiles, signing identities, and device identifiers on your Mac.
Expand Down
Binary file added packaging/robrix_logo_alpha_128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packaging/robrix_logo_alpha_256x256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packaging/robrix_logo_alpha_32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packaging/robrix_logo_alpha_48x48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packaging/robrix_logo_alpha_512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packaging/robrix_logo_alpha_64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/android/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/android/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/android/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/android/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/android/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/icon_1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 0 additions & 31 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1710,37 +1710,6 @@ impl AppMain for App {
let scope = &mut Scope::with_data(&mut self.app_state);
self.ui.handle_event(cx, event, scope);

/*
* TODO: I'd like for this to work, but it doesn't behave as expected.
* The context menu fails to draw properly when a draw event is passed to it.
* Also, once we do get this to work, we should remove the
* Hit::FingerScroll event handler in the new_message_context_menu widget.
*
// We only forward "interactive hit" events to the underlying UI view
// if none of the various overlay views are visible.
// Currently, the only overlay view that captures interactive events is
// the new message context menu.
// We always forward "non-interactive hit" events to the inner UI view.
// We check which overlay views are visible in the order of those views' z-ordering,
// such that the top-most views get a chance to handle the event first.

let new_message_context_menu = self.ui.new_message_context_menu(cx, ids!(new_message_context_menu));
let is_interactive_hit = utils::is_interactive_hit_event(event);
let is_pane_shown: bool;
if new_message_context_menu.is_currently_shown(cx) {
is_pane_shown = true;
new_message_context_menu.handle_event(cx, event, scope);
}
else {
is_pane_shown = false;
}

if !is_pane_shown || !is_interactive_hit {
// Forward the event to the inner UI view.
self.ui.handle_event(cx, event, scope);
}
*
*/
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/home/new_message_context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,12 @@ impl Widget for NewMessageContextMenu {
self.visible = false;
};

self.view.draw_walk(cx, scope, walk)
let step = self.view.draw_walk(cx, scope, walk);
if self.visible {
let main_content_area = self.view(cx, ids!(main_content)).area();
cx.block_scrolling_except_within(main_content_area);
}
step
}

fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
Expand All @@ -323,7 +328,6 @@ impl Widget for NewMessageContextMenu {
// 1. The back navigational gesture/action occurs (e.g., Back on Android),
// 2. The escape key is pressed if this menu has key focus,
// 3. The user clicks/touches outside the main_content view area.
// 4. The user scrolls anywhere.
let close_menu = {
event.back_pressed()
|| match event.hits_with_capture_overload(cx, area, true) {
Expand All @@ -344,9 +348,6 @@ impl Widget for NewMessageContextMenu {
!self.view(cx, ids!(main_content)).area().rect(cx).contains(fue.abs)
}
}
// Ignore zero-scroll events: macOS trackpad generates FingerScroll(0,0)
// on two-finger press (right-click), which would incorrectly dismiss the menu.
Hit::FingerScroll(fse) => fse.scroll.x != 0.0 || fse.scroll.y != 0.0,
_ => false,
}
};
Expand Down Expand Up @@ -651,6 +652,7 @@ impl NewMessageContextMenu {
self.details = None;
self.pending_open_gesture = None;
cx.revert_key_focus();
cx.unblock_scrolling();
self.redraw(cx);
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/home/room_context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ impl Widget for RoomContextMenu {
if self.details.is_none() {
self.visible = false;
};
self.view.draw_walk(cx, scope, walk)
let step = self.view.draw_walk(cx, scope, walk);
if self.visible {
let main_content_area = self.view(cx, ids!(main_content)).area();
cx.block_scrolling_except_within(main_content_area);
}
step
}

fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
Expand Down Expand Up @@ -186,9 +191,6 @@ impl Widget for RoomContextMenu {
!self.view(cx, ids!(main_content)).area().rect(cx).contains(fue.abs)
}
}
// Ignore zero-scroll events: macOS trackpad generates FingerScroll(0,0)
// on two-finger press (right-click), which would incorrectly dismiss the menu.
Hit::FingerScroll(fse) => fse.scroll.x != 0.0 || fse.scroll.y != 0.0,
_ => false,
}
};
Expand Down Expand Up @@ -355,6 +357,7 @@ impl RoomContextMenu {
self.details = None;
self.pending_open_gesture = None;
cx.revert_key_focus();
cx.unblock_scrolling();
self.redraw(cx);
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9736,7 +9736,8 @@ fn populate_file_message_content(
.unwrap_or_default();
// Escape caption to prevent HTML injection from untrusted message content
let caption = file_content.formatted_caption()
.map(|fb| format!("<br><i>{}</i>", htmlize::escape_text(&fb.body)))
.filter(|fb| fb.format == MessageFormat::Html)
.map(|fb| format!("<br><i>{}</i>", fb.body))
.or_else(|| file_content.caption().map(|c| format!("<br><i>{}</i>", htmlize::escape_text(c))))
.unwrap_or_default();

Expand Down Expand Up @@ -9790,6 +9791,7 @@ fn populate_audio_message_content(
))
.unwrap_or_default();
let caption = audio.formatted_caption()
.filter(|fb| fb.format == MessageFormat::Html)
.map(|fb| format!("<br><i>{}</i>", fb.body))
.or_else(|| audio.caption().map(|c| format!("<br><i>{c}</i>")))
.unwrap_or_default();
Expand Down Expand Up @@ -9841,6 +9843,7 @@ fn populate_video_message_content(
))
.unwrap_or_default();
let caption = video.formatted_caption()
.filter(|fb| fb.format == MessageFormat::Html)
.map(|fb| format!("<br><i>{}</i>", fb.body))
.or_else(|| video.caption().map(|c| format!("<br><i>{c}</i>")))
.unwrap_or_default();
Expand Down
Loading
Loading