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(
+ ""><img src=x onerror=alert(1)>");
+});
+
+// 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]