This is an automated email from the ASF dual-hosted git repository.
sarutak 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 df604f04f103 [SPARK-54857][WEBUI][TESTS] Add test ensuring user name
and app name in historypage get escaped
df604f04f103 is described below
commit df604f04f103c2df21e5aec3241f541de56aca8d
Author: Kousuke Saruta <[email protected]>
AuthorDate: Mon Dec 29 14:10:01 2025 +0900
[SPARK-54857][WEBUI][TESTS] Add test ensuring user name and app name in
historypage get escaped
### What changes were proposed in this pull request?
This PR proposes to add tests for SPARK-53337 and SPARK-54624.
### Why are the changes needed?
To ensure user name and app name in historypage get escaped.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Add new test.
### Was this patch authored or co-authored using generative AI tooling?
No.
Closes #53628 from sarutak/add-xss-test.
Authored-by: Kousuke Saruta <[email protected]>
Signed-off-by: Kousuke Saruta <[email protected]>
---
.../application_list_json_expectation.json | 15 +++++++++++
.../completed_app_list_json_expectation.json | 15 +++++++++++
.../limit_app_list_json_expectation.json | 30 ++++++++++-----------
.../minDate_app_list_json_expectation.json | 15 +++++++++++
.../minEndDate_app_list_json_expectation.json | 15 +++++++++++
.../.appstatus_local-1766844910796.crc | Bin 0 -> 8 bytes
.../appstatus_local-1766844910796 | 0
.../events_1_local-1766844910796 | 15 +++++++++++
.../spark/deploy/history/HistoryServerSuite.scala | 17 ++++++++++++
dev/.rat-excludes | 1 +
10 files changed, 108 insertions(+), 15 deletions(-)
diff --git
a/core/src/test/resources/HistoryServerExpectations/application_list_json_expectation.json
b/core/src/test/resources/HistoryServerExpectations/application_list_json_expectation.json
index eb05c2cab171..5bda5747c187 100644
---
a/core/src/test/resources/HistoryServerExpectations/application_list_json_expectation.json
+++
b/core/src/test/resources/HistoryServerExpectations/application_list_json_expectation.json
@@ -1,4 +1,19 @@
[ {
+ "attempts" : [ {
+ "appSparkVersion" : "4.2.0-SNAPSHOT",
+ "completed" : true,
+ "duration" : 2006,
+ "endTime" : "2025-12-27T14:15:12.221GMT",
+ "endTimeEpoch" : 1766844912221,
+ "lastUpdated" : "",
+ "lastUpdatedEpoch" : 0,
+ "sparkUser" : "<script>alert('XSS')</script>",
+ "startTime" : "2025-12-27T14:15:10.215GMT",
+ "startTimeEpoch" : 1766844910215
+ } ],
+ "id" : "local-1766844910796",
+ "name" : "<script>alert('XSS')</script>"
+}, {
"attempts" : [ {
"appSparkVersion" : "3.3.0-SNAPSHOT",
"completed" : true,
diff --git
a/core/src/test/resources/HistoryServerExpectations/completed_app_list_json_expectation.json
b/core/src/test/resources/HistoryServerExpectations/completed_app_list_json_expectation.json
index eb05c2cab171..5bda5747c187 100644
---
a/core/src/test/resources/HistoryServerExpectations/completed_app_list_json_expectation.json
+++
b/core/src/test/resources/HistoryServerExpectations/completed_app_list_json_expectation.json
@@ -1,4 +1,19 @@
[ {
+ "attempts" : [ {
+ "appSparkVersion" : "4.2.0-SNAPSHOT",
+ "completed" : true,
+ "duration" : 2006,
+ "endTime" : "2025-12-27T14:15:12.221GMT",
+ "endTimeEpoch" : 1766844912221,
+ "lastUpdated" : "",
+ "lastUpdatedEpoch" : 0,
+ "sparkUser" : "<script>alert('XSS')</script>",
+ "startTime" : "2025-12-27T14:15:10.215GMT",
+ "startTimeEpoch" : 1766844910215
+ } ],
+ "id" : "local-1766844910796",
+ "name" : "<script>alert('XSS')</script>"
+}, {
"attempts" : [ {
"appSparkVersion" : "3.3.0-SNAPSHOT",
"completed" : true,
diff --git
a/core/src/test/resources/HistoryServerExpectations/limit_app_list_json_expectation.json
b/core/src/test/resources/HistoryServerExpectations/limit_app_list_json_expectation.json
index 7dbab594055a..1f03d9f97395 100644
---
a/core/src/test/resources/HistoryServerExpectations/limit_app_list_json_expectation.json
+++
b/core/src/test/resources/HistoryServerExpectations/limit_app_list_json_expectation.json
@@ -1,4 +1,19 @@
[ {
+ "attempts" : [ {
+ "appSparkVersion" : "4.2.0-SNAPSHOT",
+ "completed" : true,
+ "duration" : 2006,
+ "endTime" : "2025-12-27T14:15:12.221GMT",
+ "endTimeEpoch" : 1766844912221,
+ "lastUpdated" : "",
+ "lastUpdatedEpoch" : 0,
+ "sparkUser" : "<script>alert('XSS')</script>",
+ "startTime" : "2025-12-27T14:15:10.215GMT",
+ "startTimeEpoch" : 1766844910215
+ } ],
+ "id" : "local-1766844910796",
+ "name" : "<script>alert('XSS')</script>"
+}, {
"attempts" : [ {
"appSparkVersion" : "3.3.0-SNAPSHOT",
"completed" : true,
@@ -28,19 +43,4 @@
} ],
"id" : "application_1628109047826_1317105",
"name" : "Spark shell"
-}, {
- "attempts" : [ {
- "appSparkVersion" : "3.1.0-SNAPSHOT",
- "completed" : true,
- "duration" : 363996,
- "endTime" : "2020-07-07T03:17:04.231GMT",
- "endTimeEpoch" : 1594091824231,
- "lastUpdated" : "",
- "lastUpdatedEpoch" : 0,
- "sparkUser" : "terryk",
- "startTime" : "2020-07-07T03:11:00.235GMT",
- "startTimeEpoch" : 1594091460235
- } ],
- "id" : "app-20200706201101-0003",
- "name" : "Spark shell"
} ]
diff --git
a/core/src/test/resources/HistoryServerExpectations/minDate_app_list_json_expectation.json
b/core/src/test/resources/HistoryServerExpectations/minDate_app_list_json_expectation.json
index 6df175062b5a..3e9c4b1d0f30 100644
---
a/core/src/test/resources/HistoryServerExpectations/minDate_app_list_json_expectation.json
+++
b/core/src/test/resources/HistoryServerExpectations/minDate_app_list_json_expectation.json
@@ -1,4 +1,19 @@
[ {
+ "attempts" : [ {
+ "appSparkVersion" : "4.2.0-SNAPSHOT",
+ "completed" : true,
+ "duration" : 2006,
+ "endTime" : "2025-12-27T14:15:12.221GMT",
+ "endTimeEpoch" : 1766844912221,
+ "lastUpdated" : "",
+ "lastUpdatedEpoch" : 0,
+ "sparkUser" : "<script>alert('XSS')</script>",
+ "startTime" : "2025-12-27T14:15:10.215GMT",
+ "startTimeEpoch" : 1766844910215
+ } ],
+ "id" : "local-1766844910796",
+ "name" : "<script>alert('XSS')</script>"
+}, {
"attempts" : [ {
"appSparkVersion" : "3.3.0-SNAPSHOT",
"completed" : true,
diff --git
a/core/src/test/resources/HistoryServerExpectations/minEndDate_app_list_json_expectation.json
b/core/src/test/resources/HistoryServerExpectations/minEndDate_app_list_json_expectation.json
index e084a5f9c29e..8c938661d7b2 100644
---
a/core/src/test/resources/HistoryServerExpectations/minEndDate_app_list_json_expectation.json
+++
b/core/src/test/resources/HistoryServerExpectations/minEndDate_app_list_json_expectation.json
@@ -1,4 +1,19 @@
[ {
+ "attempts" : [ {
+ "appSparkVersion" : "4.2.0-SNAPSHOT",
+ "completed" : true,
+ "duration" : 2006,
+ "endTime" : "2025-12-27T14:15:12.221GMT",
+ "endTimeEpoch" : 1766844912221,
+ "lastUpdated" : "",
+ "lastUpdatedEpoch" : 0,
+ "sparkUser" : "<script>alert('XSS')</script>",
+ "startTime" : "2025-12-27T14:15:10.215GMT",
+ "startTimeEpoch" : 1766844910215
+ } ],
+ "id" : "local-1766844910796",
+ "name" : "<script>alert('XSS')</script>"
+}, {
"attempts" : [ {
"appSparkVersion" : "3.3.0-SNAPSHOT",
"completed" : true,
diff --git
a/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/.appstatus_local-1766844910796.crc
b/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/.appstatus_local-1766844910796.crc
new file mode 100644
index 000000000000..3b7b044936a8
Binary files /dev/null and
b/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/.appstatus_local-1766844910796.crc
differ
diff --git
a/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/appstatus_local-1766844910796
b/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/appstatus_local-1766844910796
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git
a/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/events_1_local-1766844910796
b/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/events_1_local-1766844910796
new file mode 100644
index 000000000000..c9f0d10112ee
--- /dev/null
+++
b/core/src/test/resources/spark-events/eventlog_v2_local-1766844910796/events_1_local-1766844910796
@@ -0,0 +1,15 @@
+{"Event":"SparkListenerLogStart","Spark Version":"4.2.0-SNAPSHOT"}
+{"Event":"SparkListenerResourceProfileAdded","Resource Profile Id":0,"Executor
Resource Requests":{"cores":{"Resource Name":"cores","Amount":1,"Discovery
Script":"","Vendor":""},"memory":{"Resource
Name":"memory","Amount":1024,"Discovery
Script":"","Vendor":""},"offHeap":{"Resource
Name":"offHeap","Amount":0,"Discovery Script":"","Vendor":""}},"Task Resource
Requests":{"cpus":{"Resource Name":"cpus","Amount":1.0}}}
+{"Event":"SparkListenerExecutorAdded","Timestamp":1766844910927,"Executor
ID":"driver","Executor Info":{"Host":"192.168.1.109","Total Cores":10,"Log
Urls":{},"Attributes":{},"Resources":{},"Resource Profile Id":0}}
+{"Event":"SparkListenerBlockManagerAdded","Block Manager ID":{"Executor
ID":"driver","Host":"192.168.1.109","Port":64864},"Maximum
Memory":455501414,"Timestamp":1766844910942,"Maximum Onheap
Memory":455501414,"Maximum Offheap Memory":0}
+{"Event":"SparkListenerEnvironmentUpdate","JVM Information":{"Java
Home":"/Users/sarutak/.local/share/mise/installs/java/corretto-17.0.15.6.1","Java
Version":"17.0.15 (Amazon.com Inc.)","Scala Version":"version 2.13.18"},"Spark
Properties":{"spark.eventLog.enabled":"true","spark.driver.port":"64861","spark.app.name":"<script>alert('XSS')</script>","spark.hadoop.fs.s3a.vectored.read.max.merged.size":"2M","spark.scheduler.mode":"FIFO","spark.submit.pyFiles":"","spark.app.submitTime":"17668
[...]
+{"Event":"SparkListenerApplicationStart","App
Name":"<script>alert('XSS')</script>","App
ID":"local-1766844910796","Timestamp":1766844910215,"User":"<script>alert('XSS')</script>"}
+{"Event":"SparkListenerJobStart","Job ID":0,"Submission
Time":1766844911851,"Stage Infos":[{"Stage ID":0,"Stage Attempt ID":0,"Stage
Name":"reduce at SparkPi.scala:38","Number of Tasks":2,"RDD Info":[{"RDD
ID":1,"Name":"MapPartitionsRDD","Scope":"{\"id\":\"1\",\"name\":\"map\"}","Callsite":"map
at SparkPi.scala:34","Parent IDs":[0],"Storage Level":{"Use Disk":false,"Use
Memory":false,"Use Off
Heap":false,"Deserialized":false,"Replication":1},"Barrier":false,"DeterministicLevel":"DETERMIN
[...]
+{"Event":"SparkListenerStageSubmitted","Stage Info":{"Stage ID":0,"Stage
Attempt ID":0,"Stage Name":"reduce at SparkPi.scala:38","Number of
Tasks":2,"RDD Info":[{"RDD
ID":1,"Name":"MapPartitionsRDD","Scope":"{\"id\":\"1\",\"name\":\"map\"}","Callsite":"map
at SparkPi.scala:34","Parent IDs":[0],"Storage Level":{"Use Disk":false,"Use
Memory":false,"Use Off
Heap":false,"Deserialized":false,"Replication":1},"Barrier":false,"DeterministicLevel":"DETERMINATE","Number
of Partitions":2,"Number o [...]
+{"Event":"SparkListenerTaskStart","Stage ID":0,"Stage Attempt ID":0,"Task
Info":{"Task ID":0,"Index":0,"Attempt":0,"Partition ID":0,"Launch
Time":1766844912069,"Executor
ID":"driver","Host":"192.168.1.109","Locality":"PROCESS_LOCAL","Speculative":false,"Getting
Result Time":0,"Finish
Time":0,"Failed":false,"Killed":false,"Accumulables":[]}}
+{"Event":"SparkListenerTaskStart","Stage ID":0,"Stage Attempt ID":0,"Task
Info":{"Task ID":1,"Index":1,"Attempt":0,"Partition ID":1,"Launch
Time":1766844912079,"Executor
ID":"driver","Host":"192.168.1.109","Locality":"PROCESS_LOCAL","Speculative":false,"Getting
Result Time":0,"Finish
Time":0,"Failed":false,"Killed":false,"Accumulables":[]}}
+{"Event":"SparkListenerTaskEnd","Stage ID":0,"Stage Attempt ID":0,"Task
Type":"ResultTask","Task End Reason":{"Reason":"Success"},"Task Info":{"Task
ID":1,"Index":1,"Attempt":0,"Partition ID":1,"Launch
Time":1766844912079,"Executor
ID":"driver","Host":"192.168.1.109","Locality":"PROCESS_LOCAL","Speculative":false,"Getting
Result Time":0,"Finish
Time":1766844912179,"Failed":false,"Killed":false,"Accumulables":[{"ID":37,"Name":"internal.metrics.executorDeserializeTime","Update":33,"Value":
[...]
+{"Event":"SparkListenerTaskEnd","Stage ID":0,"Stage Attempt ID":0,"Task
Type":"ResultTask","Task End Reason":{"Reason":"Success"},"Task Info":{"Task
ID":0,"Index":0,"Attempt":0,"Partition ID":0,"Launch
Time":1766844912069,"Executor
ID":"driver","Host":"192.168.1.109","Locality":"PROCESS_LOCAL","Speculative":false,"Getting
Result Time":0,"Finish
Time":1766844912180,"Failed":false,"Killed":false,"Accumulables":[{"ID":37,"Name":"internal.metrics.executorDeserializeTime","Update":33,"Value":
[...]
+{"Event":"SparkListenerStageCompleted","Stage Info":{"Stage ID":0,"Stage
Attempt ID":0,"Stage Name":"reduce at SparkPi.scala:38","Number of
Tasks":2,"RDD Info":[{"RDD
ID":1,"Name":"MapPartitionsRDD","Scope":"{\"id\":\"1\",\"name\":\"map\"}","Callsite":"map
at SparkPi.scala:34","Parent IDs":[0],"Storage Level":{"Use Disk":false,"Use
Memory":false,"Use Off
Heap":false,"Deserialized":false,"Replication":1},"Barrier":false,"DeterministicLevel":"DETERMINATE","Number
of Partitions":2,"Number o [...]
+{"Event":"SparkListenerJobEnd","Job ID":0,"Completion Time":1766844912185,"Job
Result":{"Result":"JobSucceeded"}}
+{"Event":"SparkListenerApplicationEnd","Timestamp":1766844912221,"ExitCode":0}
diff --git
a/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
b/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
index 0a564f571521..c7b1b27a1b89 100644
---
a/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
+++
b/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
@@ -390,6 +390,23 @@ abstract class HistoryServerSuite extends SparkFunSuite
with BeforeAndAfter with
assert(response.contains(SPARK_VERSION))
}
+ test("SPARK-54857: XSS prevention in application and user names") {
+ implicit val formats = org.json4s.DefaultFormats
+
+ val appId = "local-1766844910796"
+ val response = getUrl(s"applications/$appId")
+ val app = JsonMethods.parse(response)
+
+ // Verify that malicious content is present in JSON (not escaped at API
level)
+ (app \ "name").extract[String] should be ("<script>alert('XSS')</script>")
+ val attempt = (app \ "attempts")(0)
+ (attempt \ "sparkUser").extract[String] should be
("<script>alert('XSS')</script>")
+
+ // Verify that the history page HTML properly escapes the content
+ val historyPage = HistoryServerSuite.getUrl(buildPageAttemptUrl(appId,
None))
+ historyPage should not include "<script>alert('XSS')</script>"
+ }
+
/**
* Verify that the security manager needed for the history server can be
instantiated
* when `spark.authenticate` is `true`, rather than raise an
`IllegalArgumentException`.
diff --git a/dev/.rat-excludes b/dev/.rat-excludes
index 06de71f723e1..725287810000 100644
--- a/dev/.rat-excludes
+++ b/dev/.rat-excludes
@@ -88,6 +88,7 @@ local-1430917381534
local-1430917381535_1
local-1430917381535_2
local-1642039451826
+eventlog_v2_local-1766844910796
DESCRIPTION
NAMESPACE
test_support/*
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]