diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 18:13:53 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 18:13:53 +0200 |
| commit | ea2c64c9a6fbc22bffbac4e2e57ce0762f5e03ad (patch) | |
| tree | 7364a91efae5e335cf3d4d48ef4847315ee163ff | |
| parent | 15dc51d86a964cc38aff23f0d47b80734a5356a8 (diff) | |
| download | quickshell-ea2c64c9a6fbc22bffbac4e2e57ce0762f5e03ad.tar.gz quickshell-ea2c64c9a6fbc22bffbac4e2e57ce0762f5e03ad.zip | |
feat(notifications): strip remote inline image sources
A notification is untrusted input. Inline <img> now renders only for
local sources; an http(s) source is removed before the RichText body is
shown, so a remote sender cannot make the shell fetch a URL. The row and
the balloon share the one sanitizer in the Notify singleton.
| -rw-r--r-- | desktop/NotificationRow.qml | 2 | ||||
| -rw-r--r-- | notifications/NotificationBalloon.qml | 2 | ||||
| -rw-r--r-- | shared/Notify.qml | 8 |
3 files changed, 10 insertions, 2 deletions
diff --git a/desktop/NotificationRow.qml b/desktop/NotificationRow.qml index 04b0c0d..923cc9e 100644 --- a/desktop/NotificationRow.qml +++ b/desktop/NotificationRow.qml @@ -92,7 +92,7 @@ Rectangle { Text { width: parent.width visible: text !== "" - text: row.notification.body || "" + text: Notify.sanitize(row.notification.body) textFormat: Text.RichText wrapMode: Text.WordWrap maximumLineCount: 2 diff --git a/notifications/NotificationBalloon.qml b/notifications/NotificationBalloon.qml index b77ca2a..bfccda3 100644 --- a/notifications/NotificationBalloon.qml +++ b/notifications/NotificationBalloon.qml @@ -123,7 +123,7 @@ Rectangle { Text { width: parent.width visible: text !== "" - text: b.notification.body || "" + text: Notify.sanitize(b.notification.body) textFormat: Text.RichText wrapMode: Text.WordWrap maximumLineCount: 3 diff --git a/shared/Notify.qml b/shared/Notify.qml index 897e50b..c79f5f5 100644 --- a/shared/Notify.qml +++ b/shared/Notify.qml @@ -65,6 +65,14 @@ Singleton { } function close(id) { root.run(["close", String(id)]); } function closeAll() { root.run(["close-all"]); } + + // Inline images are local only. A notification is untrusted input, and a + // remote <img src> would otherwise make the shell fetch a URL, which leaks + // that the notification was shown. This removes such tags before the + // RichText body renders; a local path or file:// source is left alone. + function sanitize(body) { + return (body || "").replace(/<img\b[^>]*\bsrc\s*=\s*["']?\s*https?:\/\/[^>]*>/gi, ""); + } function action(id, key) { root.run(["action", String(id), key]); } function clearHistory() { root.run(["clear-history"]); } |
