This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 380776d2c421 [MINOR][UI] Escape HTML when rendering error messages in
the Web UI tables
380776d2c421 is described below
commit 380776d2c421458a043a116f6cc2e17d49b2548f
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 ge>
### 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 48f3ce77498c..7583b99fc4e3 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 $, 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 ca5fbdc6b80c..3f0885b9c4f5 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 data-toggle-details=".stacktrace-details"
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]