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, "&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 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, "&amp;")
+    .replace(/</g, "&lt;")
+    .replace(/>/g, "&gt;")
+    .replace(/"/g, "&quot;")
+    .replace(/'/g, "&#039;");
+}
+
 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('&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