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

dongjoon-hyun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 00b61a740ed1 [SPARK-58158][CORE][UI] Escape the executor removal 
reason in `executorspage.js`
00b61a740ed1 is described below

commit 00b61a740ed1c3279ef54de4662f6c0309e68818
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Thu Jul 16 06:09:44 2026 -0700

    [SPARK-58158][CORE][UI] Escape the executor removal reason in 
`executorspage.js`
    
    ### What changes were proposed in this pull request?
    
    Escape the executor removal reason in `executorspage.js` by reusing the 
existing
    `escapeHtml` helper from `utils.js`.
    
    `formatLossReason` is the DataTables `render` callback for the 
`removeReason`
    column and returned the value unchanged. DataTables assigns the `render` 
output
    to the cell's `innerHTML`, so the reason was parsed as markup.
    
    ### Why are the changes needed?
    
    The removal reason carries strings that originate outside the driver: 
executor-side
    exception messages via `CoarseGrainedExecutorBackend`, YARN container 
diagnostics,
    K8s pod status messages, and Standalone worker exceptions. They converge on
    `ExecutorLossReason.message` and reach the UI through
    `SparkListenerExecutorRemoved`.
    
    A reason such as `"><img src=x onerror=alert(1)>` injects a tag into the 
Executors
    table. From **Apache Spark 4.3.0**, the default CSP set by 
`HttpSecurityFilter` blocks the inline handler from
    running, so this is defense-in-depth rather than a directly exploitable 
XSS. It
    still matters when CSP is disabled and in old Spark versions, and because 
the reason is persisted to the
    event log and replayed by the History Server, which may render one user's 
application to an administrator.
    
    - #57144
    
    ### Does this PR introduce _any_ user-facing change?
    
    No behavior change. Only, `Executors` UI page pages shows the removal 
reason containing HTML metacharacters is now displayed literally as text 
instead of being interpreted as markup. Other reasons are unaffected.
    
    ### How was this patch tested?
    
    Pass the CIs with newly added `ui-test/tests/executorspage.test.js`.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Opus 4.8
    
    Closes #57290 from dongjoon-hyun/SPARK-58158.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../org/apache/spark/ui/static/executorspage.js    |  6 ++-
 ui-test/tests/executorspage.test.js                | 55 ++++++++++++++++++++++
 2 files changed, 59 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/resources/org/apache/spark/ui/static/executorspage.js 
b/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
index b1eae834c73a..5cfc0165bba5 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/executorspage.js
@@ -19,12 +19,13 @@
 
 import {
   createRESTEndPointForExecutorsPage, 
createRESTEndPointForMiscellaneousProcess, createTemplateURI,
+  escapeHtml,
   formatBytes, formatDate, formatDuration, formatLogsCells,
   getStandAloneAppId,
   setDataTableDefaults
 } from './utils.js';
 
-export { setHeapHistogramEnabled, setThreadDumpEnabled };
+export { formatLossReason, setHeapHistogramEnabled, setThreadDumpEnabled };
 
 var threadDumpEnabled = false;
 var heapHistogramEnabled = false;
@@ -188,7 +189,8 @@ $(document).ready(function() {
 
 function formatLossReason(removeReason) {
   if (removeReason) {
-    return removeReason
+    // The reason originates outside the driver and DataTables renders this 
cell as HTML.
+    return escapeHtml(removeReason)
   } else {
     return ""
   }
diff --git a/ui-test/tests/executorspage.test.js 
b/ui-test/tests/executorspage.test.js
new file mode 100644
index 000000000000..409818469054
--- /dev/null
+++ b/ui-test/tests/executorspage.test.js
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 
'../../core/src/main/resources/org/apache/spark/ui/static/jquery.min.js';
+import 
'../../core/src/main/resources/org/apache/spark/ui/static/jquery.dataTables.min.js';
+import { formatLossReason } from 
'../../core/src/main/resources/org/apache/spark/ui/static/executorspage.js';
+
+/* global $ */
+
+/**
+ * @jest-environment jsdom
+ */
+
+// Injects a tag if the reason is not HTML escaped.
+const payload = '"><img src=x onerror=alert(1)>';
+
+test('formatLossReason', function () {
+  expect(formatLossReason("")).toBe("");
+  expect(formatLossReason(null)).toBe("");
+  expect(formatLossReason("Command exited with code 1")).toBe("Command exited 
with code 1");
+  expect(formatLossReason(payload)).toBe(
+    "&quot;&gt;&lt;img src=x onerror=alert(1)&gt;");
+});
+
+// The removal reason originates outside the driver: it can carry an 
executor-side exception
+// message, YARN container diagnostics, or a Kubernetes pod status message. 
DataTables assigns the
+// render output to the cell's innerHTML, so an unescaped reason would be 
parsed as markup.
+test('formatLossReason escapes the reason DataTables renders as HTML', 
function () {
+  document.body.innerHTML =
+    '<table id="t"><thead><tr><th>Reason</th></tr></thead></table>';
+
+  $('#t').DataTable({
+    data: [{removeReason: payload}],
+    columns: [{data: 'removeReason', render: formatLossReason}]
+  });
+
+  const cell = document.querySelector('#t tbody td');
+  expect(cell.querySelector('img')).toBeNull();
+  // The reason is still shown to the user, as text rather than markup.
+  expect(cell.textContent).toBe(payload);
+});


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

Reply via email to