Repository: louislam/uptime-kuma
File: server/prometheus.js (JavaScript)

Code before:
    /**
     * @param {object} monitor Monitor object to monitor
     * @param {Array<object>} tags Tags to add to the monitor
     */
    constructor(monitor, tags) {
        let sanitizedTags = this.sanitizeTags(tags);

        if (sanitizedTags.length <= 0) {
            sanitizedTags = "null";
        }

        this.monitorLabelValues = {
            monitor_name: monitor.name,
            monitor_type: monitor.type,
            monitor_url: monitor.url,
            monitor_hostname: monitor.hostname,
            monitor_port: monitor.port,
            monitor_tags: sanitizedTags
        };
    }

    /**
     * Sanitize tags to remove non-ASCII characters
     * See https://github.com/louislam/uptime-kuma/pull/4704#issuecomment-2366524692
     * @param {Array<object>} tags The tags to sanitize
     * @returns {*[]} The sanitized tags
     */
    sanitizeTags(tags) {
        const nonAsciiRegex = /[^\x00-\x7F]/g;
        const sanitizedTags = [];
        tags.forEach((tag) => {
            if (tag.name.match(nonAsciiRegex) || tag.value.match(nonAsciiRegex)) {
                // If the tag name or value contains non-ASCII characters, skip it
                return;
            }

            if (tag.value !== "") {
                sanitizedTags.push(tag.name + ":" + tag.value);
                return;
            }

            sanitizedTags.push(tag.name);
        });

        return sanitizedTags;
    }

    /**
     * Update the metrics page
     * @param {object} heartbeat Heartbeat details
     * @param {object} tlsInfo TLS details

Review comment (security):
```suggestion
     * @returns {string[]} The sanitized tags
```

Code after:
        const tags = new Set((await R.findAll("tag")).map((tag) => {
            return Prometheus.sanitizeForPrometheus(tag.name);
        }).filter((tagName) => {
            return tagName !== "";
        }).sort(this.sortTags));

        const commonLabels = [
            ...tags,
            "monitor_id",
            "monitor_name",
            "monitor_type",
            "monitor_url",
            "monitor_hostname",
            "monitor_port",
        ];

        monitorCertDaysRemaining = new PrometheusClient.Gauge({
            name: "monitor_cert_days_remaining",
            help: "The number of days remaining until the certificate expires",
            labelNames: commonLabels
        });

        monitorCertIsValid = new PrometheusClient.Gauge({
            name: "monitor_cert_is_valid",
            help: "Is the certificate still valid? (1 = Yes, 0= No)",
            labelNames: commonLabels
        });

        monitorResponseTime = new PrometheusClient.Gauge({
            name: "monitor_response_time",
            help: "Monitor Response Time (ms)",
            labelNames: commonLabels
        });

        monitorStatus = new PrometheusClient.Gauge({
            name: "monitor_status",
            help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
            labelNames: commonLabels
        });
    }

    /**
     * Sanitize a string to ensure it can be used as a Prometheus label or value.
     * See https://github.com/louislam/uptime-kuma/pull/4704#issuecomment-2366524692
     * @param {string} text The text to sanitize
     * @returns {string} The sanitized text
     */
    static sanitizeForPrometheus(text) {
        text = text.replace(/[^a-zA-Z0-9_]/g, "");
        text = text.replace(/^[^a-zA-Z_]+/, "");
        return text;