fix(rss): fix the rss endpoint having the wrong content type (#6570)
This commit is contained in:
parent
71a17c9329
commit
a36b365f4d
@ -30,6 +30,7 @@ class StatusPage extends BeanModel {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (statusPage) {
|
if (statusPage) {
|
||||||
|
response.type("application/rss+xml");
|
||||||
response.send(await StatusPage.renderRSS(statusPage, slug));
|
response.send(await StatusPage.renderRSS(statusPage, slug));
|
||||||
} else {
|
} else {
|
||||||
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
|
response.status(404).send(UptimeKumaServer.getInstance().indexHTML);
|
||||||
|
|||||||
38
test/backend-test/test-status-page.js
Normal file
38
test/backend-test/test-status-page.js
Normal file
@ -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, "?");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -159,4 +159,86 @@ test.describe("Status Page", () => {
|
|||||||
// @todo Test certificate expiry
|
// @todo Test certificate expiry
|
||||||
// @todo Test domain names
|
// @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 = "<script>alert(1)</script>";
|
||||||
|
const maliciousMonitorName2 = "x</title><script>alert(document.domain)</script><title>";
|
||||||
|
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(`<title><![CDATA[${maliciousMonitorName1} is down]]></title>`);
|
||||||
|
expect(rssContent).toContain(`<title><![CDATA[${maliciousMonitorName2} is down]]></title>`);
|
||||||
|
expect(rssContent).toContain(`<title><![CDATA[${normalMonitorName} is down]]></title>`);
|
||||||
|
|
||||||
|
// Verify RSS feed structure is valid
|
||||||
|
expect(rssContent).toContain("<?xml version=\"1.0\"");
|
||||||
|
expect(rssContent).toContain("<rss");
|
||||||
|
expect(rssContent).toContain("</rss>");
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user