This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new e3a52883dacb [MINOR][UI] Escape HTML when rendering error messages in
the Web UI tables
e3a52883dacb is described below
commit e3a52883dacb734ce4164d9049357a235dfe6366
Author: Holden Karau <[email protected]>
AuthorDate: Thu Jul 2 13:42:25 2026 -0700
[MINOR][UI] Escape HTML when rendering error messages in the Web UI tables
### What changes were proposed in this pull request?
Task error messages and stage failure reasons are rendered by the Web UI
JavaScript (the stage-page task table, and app names on the history page) by
concatenating the text directly into HTML. Error text that contains HTML
metacharacters (for example generic type signatures such as Seq<Int>, or other
angle brackets) is therefore rendered incorrectly and can break the surrounding
table markup.
### Why are the changes needed?
Table formatting can break
### Does this PR introduce _any_ user-facing change?
Not really (fixes a usable visible bug with table formatting)
### How was this patch tested?
New test added.
### Was this patch authored or co-authored using generative AI tooling?
Claude 4.8 primarily
Backport of bced71369c73f3c2e5980c6be2dc2a64bcab74db
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Holden Karau <[email protected]>
Co-Authored-By: Holden Karau <[email protected]>
---
.../org/apache/spark/ui/static/historypage.js | 14 ++------------
.../main/resources/org/apache/spark/ui/static/utils.js | 18 +++++++++++++++---
ui-test/tests/utils.test.js | 14 ++++++++++++++
3 files changed, 31 insertions(+), 15 deletions(-)
diff --git a/core/src/main/resources/org/apache/spark/ui/static/historypage.js
b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
index 853dfa708ef4..69703c2537a2 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/historypage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
@@ -17,7 +17,7 @@
/* global $, Mustache, jQuery, uiRoot */
-import {formatDuration, formatTimeMillis, stringAbbreviate} from "./utils.js";
+import {escapeHtml, formatDuration, formatTimeMillis, stringAbbreviate} from
"./utils.js";
export {setAppLimit};
@@ -27,17 +27,7 @@ var appLimit = -1;
function setAppLimit(val) {
appLimit = val;
}
-/* escape XSS */
-function escapeHtml(text) {
- if (typeof text !== 'string') return text;
- return text
- .replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
-}
-/* eslint-enable no-unused-vars*/
+/* eslint-enable no-unused-vars */
function makeIdNumeric(id) {
var strs = id.split("_");
diff --git a/core/src/main/resources/org/apache/spark/ui/static/utils.js
b/core/src/main/resources/org/apache/spark/ui/static/utils.js
index 2d4123bc75ab..f933154bc86d 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/utils.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/utils.js
@@ -17,7 +17,7 @@
export {
ConvertDurationString, createRESTEndPointForExecutorsPage,
createRESTEndPointForMiscellaneousProcess, createTemplateURI,
- errorMessageCell, errorSummary,
+ errorMessageCell, errorSummary, escapeHtml,
formatBytes, formatDate, formatDuration, formatLogsCells, formatTimeMillis,
getBaseURI, getStandAloneAppId, getTimeZone,
setDataTableDefaults, stringAbbreviate
@@ -235,6 +235,18 @@ function getBaseURI() {
return document.baseURI || document.URL;
}
+/* Escape a value for safe inclusion in HTML text content. Non-string values
are
+ * coerced to a string first; null/undefined are returned unchanged. */
+function escapeHtml(text) {
+ if (text === null || text === undefined) return text;
+ return String(text)
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
function detailsUINode(isMultiline, message) {
if (isMultiline) {
const span = '<span
onclick="this.parentNode.querySelector(\'.stacktrace-details\').classList.toggle(\'collapsed\')"
class="expand-details">+details</span>';
@@ -269,8 +281,8 @@ function errorSummary(errorMessage) {
function errorMessageCell(errorMessage) {
const [summary, isMultiline] = errorSummary(errorMessage);
- const details = detailsUINode(isMultiline, errorMessage);
- return summary + details;
+ const details = detailsUINode(isMultiline, escapeHtml(errorMessage));
+ return escapeHtml(summary) + details;
}
function stringAbbreviate(content, limit) {
diff --git a/ui-test/tests/utils.test.js b/ui-test/tests/utils.test.js
index a6815577bd82..b49f5034ff72 100644
--- a/ui-test/tests/utils.test.js
+++ b/ui-test/tests/utils.test.js
@@ -74,3 +74,17 @@ test('stringAbbreviate', function () {
expect(utils.stringAbbreviate('12345678901', 10)).toContain('1234567890...');
expect(utils.stringAbbreviate('12345678901',
10)).toContain('<pre>12345678901</pre>')
});
+
+test('errorMessageCell escapes HTML in error messages', function () {
+ const payload = '<img src=x onerror=alert(document.domain)>';
+
+ // A single-line message becomes the summary cell; it must be HTML-escaped.
+ const singleLine = utils.errorMessageCell(payload);
+ expect(singleLine).not.toContain(payload);
+ expect(singleLine).toContain('<img src=x
onerror=alert(document.domain)>');
+
+ // A multi-line message puts the payload in the expandable <pre> details.
+ const multiLine = utils.errorMessageCell('boom\n' + payload);
+ expect(multiLine).not.toContain(payload);
+ expect(multiLine).toContain('<img src=x
onerror=alert(document.domain)>');
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]