Skip to content

Commit b7fb510

Browse files
rixxstrugee
authored andcommitted
Fix XSS in organiser search
1 parent 048ba9a commit b7fb510

2 files changed

Lines changed: 43 additions & 11 deletions

File tree

doc/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Release Notes
77
=============
88

99
- :release:`2025.2.2 <2025-12-05>`
10-
- :bug:`orga` There was a bug that prevented the saving of custom fields in the organiser area when they were set to be public.
10+
:bug:`orga` There was a bug that prevented the saving of custom fields in the organiser area when they were set to be public.
1111
- :release:`2025.2.1 <2025-12-03>`
1212
- :bug:`orga:email` Emails containing schedule notifications for speakers in multilingual events could sometimes appear in mixed languages, with the general email text using the user’s preferred language and the notification section using the main event language.
1313
- :bug:`api` Submission creation in the API was broken.

src/pretalx/static/orga/js/base.js

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@ const getCookie = (name) => {
1919
return cookieValue
2020
}
2121

22+
const makeSearchEl = (tag, className, text) => {
23+
const el = document.createElement(tag)
24+
if (className) el.className = className
25+
if (text !== undefined && text !== null) el.textContent = text
26+
return el
27+
}
28+
const makeSearchTitle = (name, iconTag, iconClass) => {
29+
const title = makeSearchEl("span", "search-title")
30+
if (iconTag) {
31+
title.appendChild(makeSearchEl(iconTag, `fa fa-fw ${iconClass}`))
32+
title.append(" ", name ?? "")
33+
} else {
34+
title.appendChild(makeSearchEl("span", null, name))
35+
}
36+
return title
37+
}
38+
const makeSearchDetail = (iconClass, text) => {
39+
const detail = makeSearchEl("span", "search-detail")
40+
detail.appendChild(makeSearchEl("span", iconClass))
41+
detail.append(" ", text ?? "")
42+
return detail
43+
}
44+
2245
const initNavSearch = () => {
2346
const wrapper = document.querySelector("#nav-search-wrapper")
2447
const summary = wrapper.querySelector("summary")
@@ -56,24 +79,33 @@ const initNavSearch = () => {
5679
response.json().then((data) => {
5780
searchWrapper.querySelectorAll("li").forEach((el) => el.remove())
5881
data.results.forEach((res) => {
59-
let content = ""
82+
const a = document.createElement("a")
83+
// Only allow safe URLs — res.url comes from the server but guard against
84+
// javascript: schemes defensively.
85+
try {
86+
const parsed = new URL(res.url, window.location.origin)
87+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
88+
a.href = parsed.href
89+
}
90+
} catch (_) { /* leave href unset */ }
91+
6092
if (res.type === "organiser" || res.type === "user") {
6193
const icon = res.type === "organiser" ? "fa-users" : "fa-user"
62-
content = `<span class="search-title"><i class="fa fa-fw ${icon}"></i> ${res.name}</span>`
94+
a.appendChild(makeSearchTitle(res.name, "i", icon))
6395
} else if (res.type === "user.admin") {
64-
content += `<span class="search-title"><span>${res.name}</span></span>
65-
<span class="search-detail"><span class="fa fa-envelope-o fa-fw"></span> ${res.email}</span>`
96+
a.appendChild(makeSearchTitle(res.name))
97+
a.appendChild(makeSearchDetail("fa fa-envelope-o fa-fw", res.email))
6698
} else if (res.type === "submission" || res.type === "speaker") {
67-
content = `<span class="search-title"><span>${res.name}</span></span>
68-
<span class="search-detail"><span class="fa fa-calendar fa-fw"></span> ${res.event}</span>`
99+
a.appendChild(makeSearchTitle(res.name))
100+
a.appendChild(makeSearchDetail("fa fa-calendar fa-fw", res.event))
69101
} else if (res.type === "event") {
70-
content = `<span class="search-title"><span>${res.name}</span></span>
71-
<span class="search-detail"><span class="fa fa-users fa-fw"></span> ${res.organiser}</span>
72-
<span class="search-detail"><span class="fa fa-calendar fa-fw"></span> ${res.date_range}</span>`
102+
a.appendChild(makeSearchTitle(res.name))
103+
a.appendChild(makeSearchDetail("fa fa-users fa-fw", res.organiser))
104+
a.appendChild(makeSearchDetail("fa fa-calendar fa-fw", res.date_range))
73105
}
74106

75107
const li = document.createElement("li")
76-
li.innerHTML = `<a href="${res.url}">${content}</a>`
108+
li.appendChild(a)
77109
searchWrapper.querySelector("ul").append(li)
78110
}) /* data.results.forEach */
79111
}) /* response.json().then */

0 commit comments

Comments
 (0)