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]

Reply via email to