From 0796c4353d3d8f4bb3eb73c44bbece7b04dd8bf7 Mon Sep 17 00:00:00 2001 From: Eden Yemini Date: Sat, 25 Jan 2025 23:23:25 +0200 Subject: [PATCH 1/4] Detect URLs in monitor descriptions --- src/pages/Details.vue | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 17d32365c..f060dc81e 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -9,7 +9,7 @@
{{ monitor.id }}
-

{{ monitor.description }}

+

@@ -399,6 +399,19 @@ export default { screenshotURL() { return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime; + }, + + processedDescription() { + if (!this.monitor.description) { + return ''; + } + + const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi; + const processed = this.monitor.description.replace( + urlPattern, + url => `${this.escapeHtml(url)}` + ); + return processed; } }, @@ -656,6 +669,20 @@ export default { .replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL); this.pushMonitor.code = code; }); + }, + + /** + * Escape HTML + * @param {string} unsafe Unsafe string + * @returns {string} Safe string + */ + escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); } }, }; From 15026324334f7db7f0255108789af9438b9dd1d1 Mon Sep 17 00:00:00 2001 From: Eden Yemini Date: Sat, 25 Jan 2025 23:44:51 +0200 Subject: [PATCH 2/4] ESLint fixes --- src/pages/Details.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/Details.vue b/src/pages/Details.vue index f060dc81e..05eb84c17 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -9,7 +9,9 @@
{{ monitor.id }}
+

+
@@ -403,10 +405,10 @@ export default { processedDescription() { if (!this.monitor.description) { - return ''; + return ""; } - const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi; + const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi; const processed = this.monitor.description.replace( urlPattern, url => `${this.escapeHtml(url)}` From 1a3150351d6b2c5aca286c6fe820b1ae588e17f9 Mon Sep 17 00:00:00 2001 From: Eden Yemini Date: Mon, 27 Jan 2025 04:18:06 +0200 Subject: [PATCH 3/4] Avoid rendering HTML, detect links explicitly --- src/components/SafeLinks.vue | 65 ++++++++++++++++++++++++++++++++++++ src/pages/Details.vue | 37 ++++---------------- 2 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 src/components/SafeLinks.vue diff --git a/src/components/SafeLinks.vue b/src/components/SafeLinks.vue new file mode 100644 index 000000000..19d95cbd7 --- /dev/null +++ b/src/components/SafeLinks.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 05eb84c17..1a4f78b6e 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -9,9 +9,9 @@
{{ monitor.id }}
- -

- +

+ +

@@ -281,6 +281,7 @@ import Status from "../components/Status.vue"; import Datetime from "../components/Datetime.vue"; import CountUp from "../components/CountUp.vue"; import Uptime from "../components/Uptime.vue"; +import SafeLinks from "../components/SafeLinks.vue"; import Pagination from "v-pagination-3"; const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); import Tag from "../components/Tag.vue"; @@ -309,7 +310,8 @@ export default { Tag, CertificateInfo, PrismEditor, - ScreenshotDialog + ScreenshotDialog, + SafeLinks, }, data() { return { @@ -401,19 +403,6 @@ export default { screenshotURL() { return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime; - }, - - processedDescription() { - if (!this.monitor.description) { - return ""; - } - - const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi; - const processed = this.monitor.description.replace( - urlPattern, - url => `${this.escapeHtml(url)}` - ); - return processed; } }, @@ -671,20 +660,6 @@ export default { .replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL); this.pushMonitor.code = code; }); - }, - - /** - * Escape HTML - * @param {string} unsafe Unsafe string - * @returns {string} Safe string - */ - escapeHtml(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); } }, }; From 5df79d012919891ab0d71876ca765c682e49e792 Mon Sep 17 00:00:00 2001 From: Eden Yemini Date: Mon, 27 Jan 2025 18:26:46 +0200 Subject: [PATCH 4/4] Pass description through sanitize(marked()) --- src/components/SafeLinks.vue | 65 ------------------------------------ src/pages/Details.vue | 19 +++++++---- 2 files changed, 13 insertions(+), 71 deletions(-) delete mode 100644 src/components/SafeLinks.vue diff --git a/src/components/SafeLinks.vue b/src/components/SafeLinks.vue deleted file mode 100644 index 19d95cbd7..000000000 --- a/src/components/SafeLinks.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 1a4f78b6e..1d068b92e 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -9,9 +9,8 @@
{{ monitor.id }}
-

- -

+ +

@@ -281,13 +280,14 @@ import Status from "../components/Status.vue"; import Datetime from "../components/Datetime.vue"; import CountUp from "../components/CountUp.vue"; import Uptime from "../components/Uptime.vue"; -import SafeLinks from "../components/SafeLinks.vue"; import Pagination from "v-pagination-3"; const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); import Tag from "../components/Tag.vue"; import CertificateInfo from "../components/CertificateInfo.vue"; import { getMonitorRelativeURL } from "../util.ts"; import { URL } from "whatwg-url"; +import DOMPurify from "dompurify"; +import { marked } from "marked"; import { getResBaseURL } from "../util-frontend"; import { highlight, languages } from "prismjs/components/prism-core"; import "prismjs/components/prism-clike"; @@ -310,8 +310,7 @@ export default { Tag, CertificateInfo, PrismEditor, - ScreenshotDialog, - SafeLinks, + ScreenshotDialog }, data() { return { @@ -403,6 +402,14 @@ export default { screenshotURL() { return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime; + }, + + descriptionHTML() { + if (this.monitor.description != null) { + return DOMPurify.sanitize(marked(this.monitor.description)); + } else { + return ""; + } } },