diff --git a/routes/admin/events/list.ts b/routes/admin/events/list.ts
index e0c9c1d..18592f3 100644
--- a/routes/admin/events/list.ts
+++ b/routes/admin/events/list.ts
@@ -1,6 +1,15 @@
import { withRouteSpec } from "lib/middleware/with-winter-spec"
import { z } from "zod"
+function escapeHtml(value: string): string {
+ return value
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'")
+}
+
export default withRouteSpec({
methods: ["GET"],
jsonResponse: z.any(),
@@ -34,13 +43,13 @@ export default withRouteSpec({
.map(
({ event_type, event_id, created_at, ...rest }) => `
- | ${event_type} |
- ${new Date(
- created_at,
- ).toLocaleString()} |
- ${JSON.stringify(rest)
- .slice(1, -1)
- .replace(/"([^"]+)":/g, "$1:")} |
+ ${escapeHtml(event_type)} |
+ ${escapeHtml(new Date(created_at).toLocaleString())} |
+ ${escapeHtml(
+ JSON.stringify(rest)
+ .slice(1, -1)
+ .replace(/"([^"]+)":/g, "$1:"),
+ )} |
`,
)
diff --git a/tests/routes/admin-events-page.test.ts b/tests/routes/admin-events-page.test.ts
new file mode 100644
index 0000000..8c6a2e4
--- /dev/null
+++ b/tests/routes/admin-events-page.test.ts
@@ -0,0 +1,28 @@
+import { test, expect } from "bun:test"
+import { getTestServer } from "tests/fixtures/get-test-server"
+
+test("admin events page escapes untrusted event values", async () => {
+ const { axios } = await getTestServer()
+
+ await axios.post("/events/create", {
+ event_type: 'USER_LOGIN">',
+ file_path: '/">
.txt',
+ initiator: '