This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 1fac56144644 [MINOR][UI] Escape HTML when rendering error messages in 
the Web UI tables
1fac56144644 is described below

commit 1fac56144644ec52f5e65e53fece19b177fc0285
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, "&amp;")
-    .replace(/</g, "&lt;")
-    .replace(/>/g, "&gt;")
-    .replace(/"/g, "&quot;")
-    .replace(/'/g, "&#039;");
-}
-/* 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, "&amp;")
+    .replace(/</g, "&lt;")
+    .replace(/>/g, "&gt;")
+    .replace(/"/g, "&quot;")
+    .replace(/'/g, "&#039;");
+}
+
 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('&lt;img src=x 
onerror=alert(document.domain)&gt;');
+
+  // 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('&lt;img src=x 
onerror=alert(document.domain)&gt;');
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to