From f3d280f1b0147ec6e4295732f228e01d9cafe265 Mon Sep 17 00:00:00 2001 From: Anurag Ekkati Date: Fri, 2 Jan 2026 14:59:39 -0800 Subject: [PATCH 01/10] fix(monitor): DNS monitor hostname and other monitors URL validations Fixes Issue #6444 Summary: * DNS monitor hostname input will accept wildcard and rejects IP (Valid : *.testdns.co, Invalid : 8.8.8.8) * http, keyword, json-query, websocket, real-browser monitors will not accept wildcard hostnames in URL (Invalid : https://*.testdns.co/status) --- package-lock.json | 10 +++++++++ package.json | 3 ++- src/pages/EditMonitor.vue | 46 ++++++++++++++++++++++++++++++++++++++- src/util-frontend.js | 4 ++-- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab56e410d..5d1e8fe98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "thirty-two": "~1.0.2", "tldts": "^7.0.19", "tough-cookie": "~4.1.3", + "validator": "^13.15.26", "web-push": "^3.6.7", "ws": "^8.13.0" }, @@ -18742,6 +18743,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", diff --git a/package.json b/package.json index 987b5af52..4d4173217 100644 --- a/package.json +++ b/package.json @@ -122,8 +122,8 @@ "net-snmp": "^3.11.2", "node-cloudflared-tunnel": "~1.0.9", "node-fetch-cache": "^5.1.0", - "nodemailer": "~7.0.12", "node-radius-utils": "~1.2.0", + "nodemailer": "~7.0.12", "nostr-tools": "^2.10.4", "notp": "~2.0.3", "openid-client": "^5.4.2", @@ -149,6 +149,7 @@ "thirty-two": "~1.0.2", "tldts": "^7.0.19", "tough-cookie": "~4.1.3", + "validator": "^13.15.26", "web-push": "^3.6.7", "ws": "^8.13.0" }, diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index f72e68423..d5fb6d1a5 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -326,7 +326,7 @@ v-model="monitor.hostname" type="text" class="form-control" - :pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`" + :pattern="monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : (monitor.type === 'dns' ? null : ipOrHostnameRegexPattern)" required data-testid="hostname-input" > @@ -1330,6 +1330,8 @@ import { sleep, } from "../util.ts"; import { hostNameRegexPattern, timeDurationFormatter } from "../util-frontend"; +import isFQDN from "validator/lib/isFQDN"; +import isIP from "validator/lib/isIP"; import HiddenInput from "../components/HiddenInput.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue"; @@ -2083,6 +2085,48 @@ message HealthCheckResponse { } } + // Validate the DNS Monitor hostname input + if (this.monitor.type === "dns" && this.monitor.hostname) { + let hostname = this.monitor.hostname.trim(); + + if (isIP(hostname)) { + toast.error("Hostname cannot be an IP address"); + return false; + } + + if (!isFQDN(hostname, { + allow_wildcard: true, + require_tld: false, + allow_underscores: true, + allow_trailing_dot: true, + })) { + toast.error("Invalid hostname"); + return false; + } + } + + // Validate URL field input + if ((this.monitor.type === "http" || this.monitor.type === "keyword" || this.monitor.type === "json-query" || this.monitor.type === "websocket-upgrade" || this.monitor.type === "real-browser") && this.monitor.url) { + try { + const url = new URL(this.monitor.url); + if (url.hostname.includes("*")) { + toast.error("Wildcard hostnames are only supported for DNS monitors."); + return false; + } + if (!isFQDN(url.hostname, { + require_tld: false, + allow_underscores: true, + allow_trailing_dot: true, + }) && !isIP(url.hostname)) { + toast.error("Invalid hostname"); + return false; + } + } catch (err) { + toast.error("Invalid URL"); + return false; + } + } + return true; }, diff --git a/src/util-frontend.js b/src/util-frontend.js index fdb2a8157..66aabd75b 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.js @@ -109,10 +109,10 @@ export function getDevContainerServerHostname() { } /** - * Regex pattern fr identifying hostnames and IP addresses + * Regex pattern for identifying hostnames and IP addresses * @param {boolean} mqtt whether or not the regex should take into * account the fact that it is an mqtt uri - * @returns {RegExp} The requested regex + * @returns {string} The requested regex string */ export function hostNameRegexPattern(mqtt = false) { // mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect) From 6675ce50868d5379bedbd6923337935529e21d32 Mon Sep 17 00:00:00 2001 From: GivenBY Date: Sat, 3 Jan 2026 20:52:39 +0530 Subject: [PATCH 02/10] Fix: escape Telegram MarkdownV2 after template rendering --- server/notification-providers/telegram.js | 78 ++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index a98c326d7..5ba971d76 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -1,9 +1,84 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); +const { Liquid } = require("liquidjs"); +const { DOWN } = require("../../src/util"); class Telegram extends NotificationProvider { name = "telegram"; + /** + * Escapes special characters for Telegram MarkdownV2 format + * @param {string} text Text to escape + * @returns {string} Escaped text + */ + escapeMarkdownV2(text) { + if (!text) { + return text; + } + + // Characters that need to be escaped in MarkdownV2 + // https://core.telegram.org/bots/api#markdownv2-style + return String(text).replace(/[_*[\]()~>#+\-=|{}.!\\]/g, "\\$&"); + } + + /** + * Renders template with optional MarkdownV2 escaping + * @param {string} template The template + * @param {string} msg Base message + * @param {?object} monitorJSON Monitor details + * @param {?object} heartbeatJSON Heartbeat details + * @param {boolean} escapeMarkdown Whether to escape for MarkdownV2 + * @returns {Promise} Rendered template + */ + async renderTemplate(template, msg, monitorJSON, heartbeatJSON, escapeMarkdown = false) { + const engine = new Liquid({ + root: "./no-such-directory-uptime-kuma", + relativeReference: false, + dynamicPartials: false, + }); + + const parsedTpl = engine.parse(template); + + // Defaults + let monitorName = "Monitor Name not available"; + let monitorHostnameOrURL = "testing.hostname"; + + if (monitorJSON !== null) { + monitorName = monitorJSON.name; + monitorHostnameOrURL = this.extractAddress(monitorJSON); + } + + let serviceStatus = "⚠️ Test"; + if (heartbeatJSON !== null) { + serviceStatus = heartbeatJSON.status === DOWN ? "🔴 Down" : "✅ Up"; + } + + // Escape values only when MarkdownV2 is enabled + if (escapeMarkdown) { + msg = this.escapeMarkdownV2(msg); + monitorName = this.escapeMarkdownV2(monitorName); + monitorHostnameOrURL = this.escapeMarkdownV2(monitorHostnameOrURL); + serviceStatus = this.escapeMarkdownV2(serviceStatus); + } + + const context = { + // v1 compatibility (remove in v3) + STATUS: serviceStatus, + NAME: monitorName, + HOSTNAME_OR_URL: monitorHostnameOrURL, + + // Official variables + status: serviceStatus, + name: monitorName, + hostnameOrURL: monitorHostnameOrURL, + monitorJSON, + heartbeatJSON, + msg, + }; + + return engine.render(parsedTpl, context); + } + /** * @inheritdoc */ @@ -24,7 +99,8 @@ class Telegram extends NotificationProvider { } if (notification.telegramUseTemplate) { - params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON); + const escapeMarkdown = notification.telegramTemplateParseMode === "MarkdownV2"; + params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON, escapeMarkdown); if (notification.telegramTemplateParseMode !== "plain") { params.parse_mode = notification.telegramTemplateParseMode; From 11aef47731afd02f96d8704c8932e3f750e65649 Mon Sep 17 00:00:00 2001 From: Anurag Ekkati Date: Sat, 3 Jan 2026 14:27:36 -0800 Subject: [PATCH 03/10] Update src/pages/EditMonitor.vue Co-authored-by: Frank Elsinga --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index d5fb6d1a5..bd2a57894 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -2090,7 +2090,7 @@ message HealthCheckResponse { let hostname = this.monitor.hostname.trim(); if (isIP(hostname)) { - toast.error("Hostname cannot be an IP address"); + toast.error("DNS hostname cannot be an IP. Did you mean to use the resolver field?"); return false; } From f9694a21d9158a36cfd19ef7b642c56cfefc5b01 Mon Sep 17 00:00:00 2001 From: Anurag Ekkati Date: Sat, 3 Jan 2026 14:28:00 -0800 Subject: [PATCH 04/10] Update src/pages/EditMonitor.vue Co-authored-by: Frank Elsinga --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index bd2a57894..1cf7b8c0e 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -2100,7 +2100,7 @@ message HealthCheckResponse { allow_underscores: true, allow_trailing_dot: true, })) { - toast.error("Invalid hostname"); + toast.error("Invalid hostname. Must be a FQDN, wildcard, underscore, or end with a dot."); return false; } } From e83039f30bd66307ddeb2c9a8f77eb3e4e5cf128 Mon Sep 17 00:00:00 2001 From: GivenBY Date: Sat, 3 Jan 2026 23:05:36 +0530 Subject: [PATCH 05/10] Fix: reuse base template renderer and escape MarkdownV2 output --- server/notification-providers/telegram.js | 97 ++++++++++------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index 5ba971d76..790f06a69 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -1,7 +1,5 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { Liquid } = require("liquidjs"); -const { DOWN } = require("../../src/util"); class Telegram extends NotificationProvider { name = "telegram"; @@ -22,61 +20,29 @@ class Telegram extends NotificationProvider { } /** - * Renders template with optional MarkdownV2 escaping - * @param {string} template The template - * @param {string} msg Base message - * @param {?object} monitorJSON Monitor details - * @param {?object} heartbeatJSON Heartbeat details - * @param {boolean} escapeMarkdown Whether to escape for MarkdownV2 - * @returns {Promise} Rendered template + * Recursively escapes string properties of an object for Telegram MarkdownV2 + * @param {object|string} obj Object or string to escape + * @returns {object|string} Escaped object or string */ - async renderTemplate(template, msg, monitorJSON, heartbeatJSON, escapeMarkdown = false) { - const engine = new Liquid({ - root: "./no-such-directory-uptime-kuma", - relativeReference: false, - dynamicPartials: false, - }); - - const parsedTpl = engine.parse(template); - - // Defaults - let monitorName = "Monitor Name not available"; - let monitorHostnameOrURL = "testing.hostname"; - - if (monitorJSON !== null) { - monitorName = monitorJSON.name; - monitorHostnameOrURL = this.extractAddress(monitorJSON); + escapeObjectRecursive(obj) { + if (typeof obj === "string") { + return this.escapeMarkdownV2(obj); } + if (typeof obj === "object" && obj !== null) { + // Check if array + if (Array.isArray(obj)) { + return obj.map(item => this.escapeObjectRecursive(item)); + } - let serviceStatus = "⚠️ Test"; - if (heartbeatJSON !== null) { - serviceStatus = heartbeatJSON.status === DOWN ? "🔴 Down" : "✅ Up"; + const newObj = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + newObj[key] = this.escapeObjectRecursive(obj[key]); + } + } + return newObj; } - - // Escape values only when MarkdownV2 is enabled - if (escapeMarkdown) { - msg = this.escapeMarkdownV2(msg); - monitorName = this.escapeMarkdownV2(monitorName); - monitorHostnameOrURL = this.escapeMarkdownV2(monitorHostnameOrURL); - serviceStatus = this.escapeMarkdownV2(serviceStatus); - } - - const context = { - // v1 compatibility (remove in v3) - STATUS: serviceStatus, - NAME: monitorName, - HOSTNAME_OR_URL: monitorHostnameOrURL, - - // Official variables - status: serviceStatus, - name: monitorName, - hostnameOrURL: monitorHostnameOrURL, - monitorJSON, - heartbeatJSON, - msg, - }; - - return engine.render(parsedTpl, context); + return obj; } /** @@ -99,8 +65,29 @@ class Telegram extends NotificationProvider { } if (notification.telegramUseTemplate) { - const escapeMarkdown = notification.telegramTemplateParseMode === "MarkdownV2"; - params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON, escapeMarkdown); + let monitorJSONCopy = monitorJSON; + let heartbeatJSONCopy = heartbeatJSON; + + if (notification.telegramTemplateParseMode === "MarkdownV2") { + msg = this.escapeMarkdownV2(msg); + + if (monitorJSONCopy) { + monitorJSONCopy = this.escapeObjectRecursive(monitorJSONCopy); + } else { + // for testing monitorJSON is null, provide escaped defaults + monitorJSONCopy = { + name: this.escapeMarkdownV2("Monitor Name not available"), + hostname: this.escapeMarkdownV2("testing.hostname"), + url: this.escapeMarkdownV2("testing.hostname"), + }; + } + + if (heartbeatJSONCopy) { + heartbeatJSONCopy = this.escapeObjectRecursive(heartbeatJSONCopy); + } + } + + params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSONCopy, heartbeatJSONCopy); if (notification.telegramTemplateParseMode !== "plain") { params.parse_mode = notification.telegramTemplateParseMode; From 08d8278a755b5b7308865d92e0e70dbcbf65fbde Mon Sep 17 00:00:00 2001 From: Anurag Ekkati Date: Sat, 3 Jan 2026 23:42:03 -0800 Subject: [PATCH 06/10] fix(monitor): Addressing review comments for PR #6577 --- src/lang/en.json | 5 +++++ src/pages/EditMonitor.vue | 39 +++++++++++++++++++++++---------------- src/util-frontend.js | 17 ----------------- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index c75f38660..2f74267cc 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -276,6 +276,11 @@ "mqttWebsocketPathExplanation": "WebSocket path for MQTT over WebSocket connections (e.g., /mqtt)", "mqttWebsocketPathInvalid": "Please use a valid WebSocket Path format", "mqttHostnameTip": "Please use this format {hostnameFormat}", + "hostnameCannotBeIP": "DNS hostname cannot be an IP. Did you mean to use the resolver field?", + "invalidHostnameOrIP": "Invalid hostname or IP. Hostname must be a valid FQDN. Cannot use wildcard. Can have underscore, or end with a dot.", + "invalidDNSHostname": "Invalid hostname. Hostname must be a valid FQDN. Can be a wildcard, have underscore or end with a dot.", + "wildcardOnlyForDNS": "Wildcard hostnames are only supported for DNS monitors.", + "invalidURL": "Invalid URL", "successKeyword": "Success Keyword", "successKeywordExplanation": "MQTT Keyword that will be considered as success", "recent": "Recent", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 1cf7b8c0e..d9d9167d6 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -326,7 +326,6 @@ v-model="monitor.hostname" type="text" class="form-control" - :pattern="monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : (monitor.type === 'dns' ? null : ipOrHostnameRegexPattern)" required data-testid="hostname-input" > @@ -1329,7 +1328,7 @@ import { MIN_INTERVAL_SECOND, sleep, } from "../util.ts"; -import { hostNameRegexPattern, timeDurationFormatter } from "../util-frontend"; +import { timeDurationFormatter } from "../util-frontend"; import isFQDN from "validator/lib/isFQDN"; import isIP from "validator/lib/isIP"; import HiddenInput from "../components/HiddenInput.vue"; @@ -1419,8 +1418,6 @@ export default { acceptedWebsocketCodeOptions: [], dnsresolvetypeOptions: [], kafkaSaslMechanismOptions: [], - ipOrHostnameRegexPattern: hostNameRegexPattern(), - mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true), gameList: null, connectionStringTemplates: { "sqlserver": "Server=,;Database=;User Id=;Password=;Encrypt=;TrustServerCertificate=;Connection Timeout=", @@ -2085,32 +2082,42 @@ message HealthCheckResponse { } } - // Validate the DNS Monitor hostname input - if (this.monitor.type === "dns" && this.monitor.hostname) { + // Validate hostname field input for various monitors + if (this.monitor.hostname && [ "mqtt", "dns", "port", "ping", "steam", "gamedig", "radius", "tailscale-ping", "smtp", "snmp" ].includes(this.monitor.type)) { let hostname = this.monitor.hostname.trim(); - if (isIP(hostname)) { - toast.error("DNS hostname cannot be an IP. Did you mean to use the resolver field?"); + if (this.monitor.type === "mqtt") { + hostname = hostname.replace(/^(mqtt|ws)s?:\/\//, ""); + } + + if (this.monitor.type === "dns" && isIP(hostname)) { + toast.error(this.$t("hostnameCannotBeIP")); return false; } + // Wildcard is allowed only for DNS if (!isFQDN(hostname, { - allow_wildcard: true, + allow_wildcard: this.monitor.type === "dns", require_tld: false, allow_underscores: true, allow_trailing_dot: true, - })) { - toast.error("Invalid hostname. Must be a FQDN, wildcard, underscore, or end with a dot."); + }) && !isIP(hostname)) { + if(this.monitor.type === "dns") { + toast.error(this.$t("invalidDNSHostname")); + } else { + toast.error(this.$t("invalidHostnameOrIP")); + } return false; } } - // Validate URL field input + // Validate URL field input for various monitors if ((this.monitor.type === "http" || this.monitor.type === "keyword" || this.monitor.type === "json-query" || this.monitor.type === "websocket-upgrade" || this.monitor.type === "real-browser") && this.monitor.url) { try { const url = new URL(this.monitor.url); - if (url.hostname.includes("*")) { - toast.error("Wildcard hostnames are only supported for DNS monitors."); + // Browser can encode *.hostname.com to %2A.hostname.com + if (url.hostname.includes("*") || url.hostname.includes("%2A")) { + toast.error(this.$t("wildcardOnlyForDNS")); return false; } if (!isFQDN(url.hostname, { @@ -2118,11 +2125,11 @@ message HealthCheckResponse { allow_underscores: true, allow_trailing_dot: true, }) && !isIP(url.hostname)) { - toast.error("Invalid hostname"); + toast.error(this.$t("invalidHostnameOrIP")); return false; } } catch (err) { - toast.error("Invalid URL"); + toast.error(this.$t("invalidURL")); return false; } } diff --git a/src/util-frontend.js b/src/util-frontend.js index 66aabd75b..5912620b5 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.js @@ -108,23 +108,6 @@ export function getDevContainerServerHostname() { return CODESPACE_NAME + "-3001." + GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; } -/** - * Regex pattern for identifying hostnames and IP addresses - * @param {boolean} mqtt whether or not the regex should take into - * account the fact that it is an mqtt uri - * @returns {string} The requested regex string - */ -export function hostNameRegexPattern(mqtt = false) { - // mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect) - const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?"; - // Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/ - const ipRegexPattern = `((^${mqtt ? mqttSchemeRegexPattern : ""}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$))`; - // Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address - const hostNameRegexPattern = `^${mqtt ? mqttSchemeRegexPattern : ""}([a-zA-Z0-9])?(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])(\\.)?$`; - - return `${ipRegexPattern}|${hostNameRegexPattern}`; -} - /** * Get the tag color options * Shared between components From ed8051f9e37a3bb0d87168c8c169ae62c1ca1daf Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 07:45:05 +0000 Subject: [PATCH 07/10] [autofix.ci] apply automated fixes --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index d9d9167d6..08b5d82d4 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -2102,7 +2102,7 @@ message HealthCheckResponse { allow_underscores: true, allow_trailing_dot: true, }) && !isIP(hostname)) { - if(this.monitor.type === "dns") { + if (this.monitor.type === "dns") { toast.error(this.$t("invalidDNSHostname")); } else { toast.error(this.$t("invalidHostnameOrIP")); From 3f0a7e70ece0a6e1cb55306b6276921767346da4 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Sun, 4 Jan 2026 18:01:09 +0100 Subject: [PATCH 08/10] Apply suggestions from code review --- src/pages/EditMonitor.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 08b5d82d4..78bf28396 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -2083,7 +2083,7 @@ message HealthCheckResponse { } // Validate hostname field input for various monitors - if (this.monitor.hostname && [ "mqtt", "dns", "port", "ping", "steam", "gamedig", "radius", "tailscale-ping", "smtp", "snmp" ].includes(this.monitor.type)) { + if ([ "mqtt", "dns", "port", "ping", "steam", "gamedig", "radius", "tailscale-ping", "smtp", "snmp" ].includes(this.monitor.type) && this.monitor.hostname) { let hostname = this.monitor.hostname.trim(); if (this.monitor.type === "mqtt") { @@ -2112,7 +2112,7 @@ message HealthCheckResponse { } // Validate URL field input for various monitors - if ((this.monitor.type === "http" || this.monitor.type === "keyword" || this.monitor.type === "json-query" || this.monitor.type === "websocket-upgrade" || this.monitor.type === "real-browser") && this.monitor.url) { + if (["http", "keyword", "json-query", "websocket-upgrade", "real-browser"].includes(this.monitor.type) && this.monitor.url) { try { const url = new URL(this.monitor.url); // Browser can encode *.hostname.com to %2A.hostname.com From f93c3021cfd61d8c5b4920f4c316773ca2b7a1d1 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 17:02:22 +0000 Subject: [PATCH 09/10] [autofix.ci] apply automated fixes --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 78bf28396..29eeb67f8 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -2112,7 +2112,7 @@ message HealthCheckResponse { } // Validate URL field input for various monitors - if (["http", "keyword", "json-query", "websocket-upgrade", "real-browser"].includes(this.monitor.type) && this.monitor.url) { + if ([ "http", "keyword", "json-query", "websocket-upgrade", "real-browser" ].includes(this.monitor.type) && this.monitor.url) { try { const url = new URL(this.monitor.url); // Browser can encode *.hostname.com to %2A.hostname.com From 65cadead3e3877d1a8fee6896f9de1f504183c8b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 5 Jan 2026 10:01:42 +0800 Subject: [PATCH 10/10] Update to 2.1.0-beta.1 (#6583) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 196ea902c..f07c2b7fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "2.1.0-beta.0", + "version": "2.1.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "2.1.0-beta.0", + "version": "2.1.0-beta.1", "license": "MIT", "dependencies": { "@grpc/grpc-js": "~1.8.22", diff --git a/package.json b/package.json index 0a81863f1..1fff55628 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "2.1.0-beta.0", + "version": "2.1.0-beta.1", "license": "MIT", "repository": { "type": "git",