Skip to content

Commit e9d4344

Browse files
committed
fix: Potential XSS introduced by contributor
1 parent 2531560 commit e9d4344

File tree

9 files changed

+152
-350
lines changed

9 files changed

+152
-350
lines changed

src/settings.md

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -141,44 +141,6 @@ Let's customize your experience in our Bot Designer For Discord wiki world!
141141
Added!
142142
```
143143

144-
<div class="settingembed">
145-
<h2>Code Highlighting</h2>
146-
<p>Input your highlighting code here! Reload the page when you finish.</p>
147-
<button class="resetSettingButton" id="resetHG" onClick="resetHGInput()" title="Reset" aria-label="Reset">
148-
<i class="fa fa-refresh" aria-hidden="true"></i>
149-
</button>
150-
<div class="codehighlighting">
151-
<button id="copyHG" onClick="copyHGInput()" class="hgButton">
152-
<p><i class="fa fa-clipboard" aria-hidden="true"></i> Copy</p>
153-
</button>
154-
<button id="reloadHG" onClick="reloadHGPage()" class="hgButton">
155-
<p><i class="fa fa-circle-o-notch" aria-hidden="true"></i> Save and reload</p>
156-
</button>
157-
<button class="hgButton">
158-
<p><a href="https://www.youtube.com/watch?v=xvFZjo5PgG0"><i class="fa fa-book" aria-hidden="true"></i> What is this?</a></p>
159-
</button>
160-
<textarea id="jsonhginput" oninput="updateCodeHG()" maxlength="25000"></textarea>
161-
</div>
162-
<p class="charCount">0 / 25000</p>
163-
</div>
164-
165-
```
166-
$nomention
167-
$allowUserMentions[]
168-
$reply
169-
$botTyping
170-
171-
$try
172-
$sendMessage[Hello, $username 👋, ping: $ping ms]
173-
$if[$toLowercase[$message]==secret]
174-
$footer[BDFD 🤩]
175-
$endif
176-
$catch
177-
$sendMessage[Bye!]
178-
$botLeave $c[Magic😉]
179-
$endtry
180-
```
181-
182144
<div class="importantReset">
183145
<button class="resetToDefault" onmousedown="resetAllHover()" onmouseup="resettAllNone()" onmouseleave="resetAllLeave()">
184146
<span>Reset to default settings</span>

src/theme/book.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ if (window.playground_copyable) {
2929
clipButton.className = "fa fa-regular fa-paste clip-button";
3030
clipButton.title = "Copy";
3131
clipButton.setAttribute("aria-label", clipButton.title);
32-
clipButton.innerHTML = '<i class="tooltiptext"></i>';
32+
const tooltipEl = document.createElement('i');
33+
tooltipEl.className = 'tooltiptext';
34+
clipButton.appendChild(tooltipEl);
3335

3436
const wrapButton = document.createElement("button");
3537
wrapButton.className = "fa fa-solid fa-paragraph wrap-button";
@@ -223,12 +225,12 @@ if (window.playground_copyable) {
223225
const clipButtons = document.querySelectorAll(".clip-button");
224226

225227
function hideTooltip(elem) {
226-
elem.firstChild.innerText = "";
228+
elem.firstChild.textContent = "";
227229
elem.className = "fa far fa-clipboard clip-button";
228230
}
229231

230232
function showTooltip(elem, msg) {
231-
elem.firstChild.innerText = msg;
233+
elem.firstChild.textContent = msg;
232234
elem.className = "fa far fa-clipboard tooltipped";
233235
}
234236

src/theme/breadcrumbs.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,38 @@ for (let i = 0; i < paths.length; i++) {
6464
}
6565
}
6666

67-
document.write(`<a href="${root}">Home</a><p>/</p>`);
68-
breadcrumbLinks.forEach((link, index) => {
69-
if (link.href) {
70-
document.write(`<a href="${link.href}">${link.name}</a>`);
71-
if (index < breadcrumbLinks.length - 1) {
72-
// Add separator if not last
73-
document.write(`<p>/</p>`);
74-
}
75-
} else if (link.name !== "Introduction") {
76-
document.write(`<a>${link.name}</a>`);
67+
// Helper to safely create elements
68+
function createBreadcrumbLink(href, text) {
69+
const link = document.createElement('a');
70+
if (href) {
71+
link.href = href;
7772
}
78-
});
73+
link.textContent = text;
74+
return link;
75+
}
76+
77+
function createSeparator() {
78+
const sep = document.createElement('p');
79+
sep.textContent = '/';
80+
return sep;
81+
}
82+
83+
// Build breadcrumbs using DOM methods instead of document.write
84+
(function renderBreadcrumbs() {
85+
const container = document.currentScript.parentElement;
86+
87+
// Home link
88+
container.appendChild(createBreadcrumbLink(root, 'Home'));
89+
container.appendChild(createSeparator());
90+
91+
breadcrumbLinks.forEach((link, index) => {
92+
if (link.href) {
93+
container.appendChild(createBreadcrumbLink(link.href, link.name));
94+
if (index < breadcrumbLinks.length - 1) {
95+
container.appendChild(createSeparator());
96+
}
97+
} else if (link.name !== "Introduction") {
98+
container.appendChild(createBreadcrumbLink(null, link.name));
99+
}
100+
});
101+
})();

src/theme/livetime.js

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,54 @@ document.addEventListener("DOMContentLoaded", function () {
1313

1414
var dayElement = document.getElementById("day-mark");
1515
if (dayElement) {
16-
dayElement.innerHTML = "Current Day: " + day;
16+
dayElement.textContent = "Current Day: " + day;
1717
}
1818

1919
var unixElement = document.getElementById("unix-mark");
2020
if (unixElement) {
21-
unixElement.innerHTML = "Current Unix-time: " + unixTimeSec;
21+
unixElement.textContent = "Current Unix-time: " + unixTimeSec;
2222
}
2323

2424
var moreUnixElement = document.getElementById("moreunix-mark");
2525
if (moreUnixElement) {
26-
moreUnixElement.innerHTML = `Unix Timestamp<br>
27-
In Seconds - ${unixTimeSec}<br>
28-
In Milliseconds - ${unixTimeMs}<br>
29-
In Nanoseconds - ${unixTimeMs * 1000000}`;
26+
moreUnixElement.textContent = '';
27+
moreUnixElement.appendChild(document.createTextNode('Unix Timestamp'));
28+
moreUnixElement.appendChild(document.createElement('br'));
29+
moreUnixElement.appendChild(document.createTextNode('In Seconds - ' + unixTimeSec));
30+
moreUnixElement.appendChild(document.createElement('br'));
31+
moreUnixElement.appendChild(document.createTextNode('In Milliseconds - ' + unixTimeMs));
32+
moreUnixElement.appendChild(document.createElement('br'));
33+
moreUnixElement.appendChild(document.createTextNode('In Nanoseconds - ' + (unixTimeMs * 1000000)));
3034
}
3135

3236
var secondElement = document.getElementById("second-mark");
3337
if (secondElement) {
34-
secondElement.innerHTML = "Current Second: " + second;
38+
secondElement.textContent = "Current Second: " + second;
3539
}
3640

3741
var minuteElement = document.getElementById("minute-mark");
3842
if (minuteElement) {
39-
minuteElement.innerHTML = "Current Minute: " + minute;
43+
minuteElement.textContent = "Current Minute: " + minute;
4044
}
4145

4246
var hourElement = document.getElementById("hour-mark");
4347
if (hourElement) {
44-
hourElement.innerHTML = "Current Hour: " + hour;
48+
hourElement.textContent = "Current Hour: " + hour;
4549
}
4650

4751
var yearElement = document.getElementById("year-mark");
4852
if (yearElement) {
49-
yearElement.innerHTML = "Current Year: " + year;
53+
yearElement.textContent = "Current Year: " + year;
5054
}
5155

5256
var monthElement = document.getElementById("month-mark");
5357
if (monthElement) {
54-
monthElement.innerHTML = "Current Month: " + month;
58+
monthElement.textContent = "Current Month: " + month;
5559
}
5660

5761
var dateElement = document.getElementById("date-mark");
5862
if (dateElement) {
59-
dateElement.innerHTML = "Current Date: " + formattedDate;
63+
dateElement.textContent = "Current Date: " + formattedDate;
6064
}
6165

6266
function getFormattedTime(timezone) {
@@ -75,7 +79,10 @@ In Nanoseconds - ${unixTimeMs * 1000000}`;
7579
var utcTime = getFormattedTime("UTC");
7680
var moscowTime = getFormattedTime("Europe/Moscow");
7781
var utcDay = now.getUTCDate();
78-
79-
timeElement.innerHTML = `New York Time: ${utcTime}, ${utcDay}<br>Moscow Time: ${moscowTime}, ${utcDay}`;
82+
83+
timeElement.textContent = '';
84+
timeElement.appendChild(document.createTextNode('New York Time: ' + utcTime + ', ' + utcDay));
85+
timeElement.appendChild(document.createElement('br'));
86+
timeElement.appendChild(document.createTextNode('Moscow Time: ' + moscowTime + ', ' + utcDay));
8087
}
81-
});
88+
});

src/theme/playground.js

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
1+
// Helper to escape HTML special characters
2+
function escapeHTML(str) {
3+
const div = document.createElement('div');
4+
div.textContent = str;
5+
return div.innerHTML;
6+
}
7+
8+
// Helper to create error message element safely
9+
function createErrorMessage(functionName, lineNumber, position, errorText) {
10+
const playOutput = document.getElementById('play-output');
11+
playOutput.textContent = '';
12+
13+
const container = document.createDocumentFragment();
14+
15+
container.appendChild(document.createTextNode('\u274C Function '));
16+
17+
const funcNameEl = document.createElement('span');
18+
funcNameEl.id = 'errorFunctionName';
19+
funcNameEl.textContent = functionName;
20+
container.appendChild(funcNameEl);
21+
22+
container.appendChild(document.createTextNode(' at '));
23+
24+
const lineNumEl = document.createElement('span');
25+
lineNumEl.id = 'errorLineNumber';
26+
lineNumEl.textContent = `${lineNumber}:${position}`;
27+
container.appendChild(lineNumEl);
28+
29+
container.appendChild(document.createTextNode(` returned an error: ${errorText}`));
30+
31+
playOutput.appendChild(container);
32+
}
33+
134
// Math
235
function handlePlaygroundInput(inputValue, functionName, operation) {
336
const playOutput = document.getElementById('play-output');
437

538
if (!isNaN(inputValue) && inputValue !== "") {
639
if (functionName === '$sqrt' && parseFloat(inputValue) < 0) {
7-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">1:${functionName.length + 4}</p> returned an error: the input number can't be negative`;
40+
createErrorMessage(functionName, 1, functionName.length + 4, "the input number can't be negative");
841
} else {
942
const result = operation(inputValue);
1043
playOutput.textContent = `Result: ${result}`;
@@ -13,21 +46,21 @@ function handlePlaygroundInput(inputValue, functionName, operation) {
1346
if (inputValue === "") {
1447
outputEmptyValueError(functionName, 1, 1);
1548
} else {
16-
let nonNumericIndex = inputValue.search(/[^0-9\.]/);
49+
let nonNumericIndex = inputValue.search(/[^0-9\.]/);
1750
nonNumericIndex = nonNumericIndex === -1 ? inputValue.length : nonNumericIndex + functionName.length + 3;
18-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">1:${nonNumericIndex}</p> returned an error: expected integer in position 1, got '${inputValue}'`;
51+
createErrorMessage(functionName, 1, nonNumericIndex, `expected integer in position 1, got '${escapeHTML(inputValue)}'`);
1952
}
2053
}
2154
}
2255

2356
// $ceil[]
2457
function ceilPlayground(inputValue) {
25-
handlePlaygroundInput(inputValue, '$ceil', Math.ceil);
58+
handlePlaygroundInput(inputValue, '$ceil', Math.ceil);
2659
}
2760

2861
// $floor[]
2962
function floorPlayground(inputValue) {
30-
handlePlaygroundInput(inputValue, '$floor', Math.floor);
63+
handlePlaygroundInput(inputValue, '$floor', Math.floor);
3164
}
3265

3366
// $sqrt[]
@@ -37,7 +70,7 @@ function sqrtPlayground(inputValue) {
3770

3871
// $round[]
3972
function roundPlayground(inputValue) {
40-
handlePlaygroundInput(inputValue, '$round', Math.round);
73+
handlePlaygroundInput(inputValue, '$round', Math.round);
4174
}
4275

4376
// $charCount[]
@@ -64,7 +97,7 @@ function argCountPlayground(inputValue) {
6497
// $isNumber[]
6598
function isNumberPlayground(inputValue) {
6699
const playOutput = document.getElementById('play-output');
67-
playOutput.textContent = `Is number? ${!isNaN(parseFloat(inputValue)) && isFinite(inputValue)}`;
100+
playOutput.textContent = `Is number? ${!isNaN(parseFloat(inputValue)) && isFinite(inputValue)}`;
68101
}
69102

70103
// $isInteger[]
@@ -90,42 +123,51 @@ function isBooleanPlayground(inputValue) {
90123
playOutput.textContent = `Is boolean? ${booleanValues.includes(inputValue)}`;
91124
}
92125

93-
function preserveLineBreaks(text) {
94-
return text.replace(/\n/g, '<br>');
95-
}
96-
97126
function limitLines(text, maxLines) {
98127
const lines = text.split('\n');
99128
return lines.slice(0, maxLines).join('\n');
100129
}
101130

131+
// Helper to safely display multiline text
132+
function displayMultilineText(element, text) {
133+
element.textContent = '';
134+
const lines = text.split('\n');
135+
lines.forEach((line, index) => {
136+
element.appendChild(document.createTextNode(line));
137+
if (index < lines.length - 1) {
138+
element.appendChild(document.createElement('br'));
139+
}
140+
});
141+
}
142+
102143
// $trimSpace[]
103144
function trimSpacePlayground(inputValue) {
104145
const playOutput = document.getElementById('play-output');
105-
playOutput.innerHTML = preserveLineBreaks(limitLines(inputValue.trim(), 20));
146+
displayMultilineText(playOutput, limitLines(inputValue.trim(), 20));
106147
editInputHeight()
107148
}
108149

109150
// $toLowercase[]
110151
function toLowercasePlayground(inputValue) {
111152
const playOutput = document.getElementById('play-output');
112-
playOutput.innerHTML = preserveLineBreaks(limitLines(inputValue.toLowerCase(), 20));
153+
displayMultilineText(playOutput, limitLines(inputValue.toLowerCase(), 20));
113154
editInputHeight()
114155
}
115156

116157
// $toUppercase[]
117158
function toUppercasePlayground(inputValue) {
118159
const playOutput = document.getElementById('play-output');
119-
playOutput.innerHTML = preserveLineBreaks(limitLines(inputValue.toUpperCase(), 20));
160+
displayMultilineText(playOutput, limitLines(inputValue.toUpperCase(), 20));
120161
editInputHeight()
121162
}
122163

123164
// $toTitleCase[]
124165
function toTitleCasePlayground(inputValue) {
125166
const playOutput = document.getElementById('play-output');
126-
playOutput.innerHTML = preserveLineBreaks(limitLines(inputValue.replace(/\w\S*/g, (word) =>
127-
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
128-
), 20));
167+
const titleCased = inputValue.replace(/\w\S*/g, (word) =>
168+
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
169+
);
170+
displayMultilineText(playOutput, limitLines(titleCased, 20));
129171
editInputHeight()
130172
}
131173

@@ -139,11 +181,11 @@ function randomStringPlayground(inputValue) {
139181
} else if (isNaN(inputValue)) {
140182
let nonNumericIndex = inputValue.search(/[^0-9]/);
141183
nonNumericIndex = nonNumericIndex === -1 ? inputValue.length : nonNumericIndex + functionName.length + 3;
142-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">1:${nonNumericIndex}</p> returned an error: expected integer in position 1, got '${inputValue}'`;
184+
createErrorMessage(functionName, 1, nonNumericIndex, `expected integer in position 1, got '${escapeHTML(inputValue)}'`);
143185
} else if (parseInt(inputValue) > 10) {
144-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">1:${functionName.length + 3}</p> returned an error: String length has to be leser than 10`;
145-
} else if (parseInt(inputValue) < 1) {
146-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">1:${functionName.length + 3}</p> returned an error: String length has to be bigger than 0`;
186+
createErrorMessage(functionName, 1, functionName.length + 3, "String length has to be leser than 10");
187+
} else if (parseInt(inputValue) < 1) {
188+
createErrorMessage(functionName, 1, functionName.length + 3, "String length has to be bigger than 0");
147189
} else {
148190
let length = parseInt(inputValue);
149191
playOutput.textContent = `Random String: ` + generateRandomString(length);
@@ -162,8 +204,7 @@ function generateRandomString(length) {
162204

163205
// Empty value error
164206
function outputEmptyValueError(functionName, lineNumber, position) {
165-
const playOutput = document.getElementById('play-output');
166-
playOutput.innerHTML = `❌ Function <p id="errorFunctionName">${functionName}</p> at <p id="errorLineNumber">${lineNumber}:${functionName.length + 2}</p> returned an error: expected valid value in position ${position}, got empty value`;
207+
createErrorMessage(functionName, lineNumber, functionName.length + 2, `expected valid value in position ${position}, got empty value`);
167208
}
168209

169210
// Better input size for large values

0 commit comments

Comments
 (0)