diff --git a/server/model/status_page.js b/server/model/status_page.js
index 7087e4e09..0fef5caa8 100644
--- a/server/model/status_page.js
+++ b/server/model/status_page.js
@@ -30,6 +30,7 @@ class StatusPage extends BeanModel {
]);
if (statusPage) {
+ response.type("application/rss+xml");
response.send(await StatusPage.renderRSS(statusPage, slug));
} else {
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
diff --git a/test/backend-test/test-status-page.js b/test/backend-test/test-status-page.js
new file mode 100644
index 000000000..fe3fed2a5
--- /dev/null
+++ b/test/backend-test/test-status-page.js
@@ -0,0 +1,38 @@
+const { describe, test } = require("node:test");
+const assert = require("node:assert");
+const StatusPage = require("../../server/model/status_page");
+const { STATUS_PAGE_ALL_UP, STATUS_PAGE_ALL_DOWN, STATUS_PAGE_PARTIAL_DOWN, STATUS_PAGE_MAINTENANCE } = require("../../src/util");
+
+describe("StatusPage", () => {
+ describe("getStatusDescription()", () => {
+ test("returns 'No Services' when status is -1", () => {
+ const description = StatusPage.getStatusDescription(-1);
+ assert.strictEqual(description, "No Services");
+ });
+
+ test("returns 'All Systems Operational' when all services are up", () => {
+ const description = StatusPage.getStatusDescription(STATUS_PAGE_ALL_UP);
+ assert.strictEqual(description, "All Systems Operational");
+ });
+
+ test("returns 'Partially Degraded Service' when some services are down", () => {
+ const description = StatusPage.getStatusDescription(STATUS_PAGE_PARTIAL_DOWN);
+ assert.strictEqual(description, "Partially Degraded Service");
+ });
+
+ test("returns 'Degraded Service' when all services are down", () => {
+ const description = StatusPage.getStatusDescription(STATUS_PAGE_ALL_DOWN);
+ assert.strictEqual(description, "Degraded Service");
+ });
+
+ test("returns 'Under maintenance' when status page is in maintenance", () => {
+ const description = StatusPage.getStatusDescription(STATUS_PAGE_MAINTENANCE);
+ assert.strictEqual(description, "Under maintenance");
+ });
+
+ test("returns '?' for unknown status values", () => {
+ const description = StatusPage.getStatusDescription(999);
+ assert.strictEqual(description, "?");
+ });
+ });
+});
diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js
index 1964f92b5..1b86d5137 100644
--- a/test/e2e/specs/status-page.spec.js
+++ b/test/e2e/specs/status-page.spec.js
@@ -159,4 +159,86 @@ test.describe("Status Page", () => {
// @todo Test certificate expiry
// @todo Test domain names
+ test("RSS feed escapes malicious monitor names", async ({ page }, testInfo) => {
+ test.setTimeout(60000);
+
+ // Test various XSS payloads in monitor names
+ const maliciousMonitorName1 = "";
+ const maliciousMonitorName2 = "x
";
+ const normalMonitorName = "Production API Server";
+
+ await page.goto("./add");
+ await login(page);
+
+ // Create first monitor with script tag payload
+ await expect(page.getByTestId("monitor-type-select")).toBeVisible();
+ await page.getByTestId("monitor-type-select").selectOption("http");
+ await page.getByTestId("friendly-name-input").fill(maliciousMonitorName1);
+ await page.getByTestId("url-input").fill("https://malicious1.example.com");
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ // Create second monitor with title breakout payload
+ await page.goto("./add");
+ await page.getByTestId("monitor-type-select").selectOption("http");
+ await page.getByTestId("friendly-name-input").fill(maliciousMonitorName2);
+ await page.getByTestId("url-input").fill("https://malicious2.example.com");
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ // Create third monitor with normal name
+ await page.goto("./add");
+ await page.getByTestId("monitor-type-select").selectOption("http");
+ await page.getByTestId("friendly-name-input").fill(normalMonitorName);
+ await page.getByTestId("url-input").fill("https://normal.example.com");
+ await page.getByTestId("save-button").click();
+ await page.waitForURL("/dashboard/*");
+
+ // Create a status page
+ await page.goto("./add-status-page");
+ await page.getByTestId("name-input").fill("Security Test");
+ await page.getByTestId("slug-input").fill("security-test");
+ await page.getByTestId("submit-button").click();
+ await page.waitForURL("/status/security-test?edit");
+
+ // Add a group and all monitors
+ await page.getByTestId("add-group-button").click();
+ await page.getByTestId("group-name").fill("Test Group");
+
+ // Add all three monitors
+ await page.getByTestId("monitor-select").click();
+ await page.getByTestId("monitor-select").getByRole("option", { name: maliciousMonitorName1 }).click();
+ await page.getByTestId("monitor-select").click();
+ await page.getByTestId("monitor-select").getByRole("option", { name: maliciousMonitorName2 }).click();
+ await page.getByTestId("monitor-select").click();
+ await page.getByTestId("monitor-select").getByRole("option", { name: normalMonitorName }).click();
+
+ await page.getByTestId("save-button").click();
+ await expect(page.getByTestId("edit-sidebar")).toHaveCount(0);
+
+ // Fetch the RSS feed
+ const rssResponse = await page.request.get("/status/security-test/rss");
+ expect(rssResponse.status()).toBe(200);
+ expect(rssResponse.headers()["content-type"]).toBe("application/rss+xml; charset=utf-8");
+ expect(rssResponse.ok()).toBeTruthy();
+
+ const rssContent = await rssResponse.text();
+
+ // Attach RSS content for inspection
+ await testInfo.attach("rss-feed.xml", {
+ body: rssContent,
+ contentType: "application/xml"
+ });
+
+ // Verify all payloads are escaped using CDATA
+ expect(rssContent).toContain(``);
+ expect(rssContent).toContain(``);
+ expect(rssContent).toContain(``);
+
+ // Verify RSS feed structure is valid
+ expect(rssContent).toContain("");
+ });
+
});