diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 18:20:33 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 18:20:33 +0200 |
| commit | 9c65aeb157fe16ab5a651f39c92902440f2d5344 (patch) | |
| tree | c07d216503c4d278b318d61987bb1998d2bd671b /shared | |
| parent | 29886e00273127c228054e2b0097e12fe132c07f (diff) | |
| download | quickshell-9c65aeb157fe16ab5a651f39c92902440f2d5344.tar.gz quickshell-9c65aeb157fe16ab5a651f39c92902440f2d5344.zip | |
The sanitizer matched the first \bsrc anywhere in the tag, so a
data-src="file:///tmp/decoy" before a remote src won the match and kept
the whole tag while Qt, which ignores data-src, fetched the remote
image. Scan every src assignment in the tag and keep it only if all of
them are local. Also set cache: false on the balloon preview, since the
daemon overwrites the same path on a replace and QQuickPixmapCache keys
on URL, so a replaced notification could show the previous image.
Diffstat (limited to 'shared')
| -rw-r--r-- | shared/Notify.qml | 20 |
1 files changed, 13 insertions, 7 deletions
diff --git a/shared/Notify.qml b/shared/Notify.qml index 237d8c9..1abe57d 100644 --- a/shared/Notify.qml +++ b/shared/Notify.qml @@ -70,9 +70,11 @@ Singleton { // remote <img src> (http(s), protocol-relative //host, ftp, data, or an // entity-encoded scheme) would make the shell fetch or embed something the // sender chose, which leaks that the notification was shown. Deny by - // default: a tag survives only if its src decodes to a file: URL or a - // leading-slash absolute path. Entities are decoded before the test, so an - // encoded scheme cannot slip past. + // default: a tag survives only if every src it carries decodes to a file: + // URL or a leading-slash absolute path. Every src assignment is checked, + // not just the first, so a decoy attribute cannot shadow a remote one; + // entities are decoded before the test, so an encoded scheme cannot slip + // past. function sanitize(body) { function decode(s) { return s.replace(/&(?:#x([0-9a-f]+)|#(\d+)|(amp|colon|sol|tab|quot));/gi, @@ -83,10 +85,14 @@ Singleton { }); } return (body || "").replace(/<img\b[^>]*>/gi, function (tag) { - const m = tag.match(/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i); - if (!m) return ""; - const src = decode(m[1] !== undefined ? m[1] : m[2] !== undefined ? m[2] : m[3]); - return /^file:/i.test(src) || (src.charAt(0) === "/" && src.charAt(1) !== "/") ? tag : ""; + const re = /(?:^|\s)src\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi; + let m, found = false, local = true; + while ((m = re.exec(tag))) { + found = true; + const src = decode(m[1] !== undefined ? m[1] : m[2] !== undefined ? m[2] : m[3]); + if (!(/^file:/i.test(src) || (src.charAt(0) === "/" && src.charAt(1) !== "/"))) local = false; + } + return (found && local) ? tag : ""; }); } |
