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

dongjoon-hyun 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 484cd7148a01 [SPARK-57589][CORE] Support 
`spark.ui.contentSecurityPolicy.enabled`
484cd7148a01 is described below

commit 484cd7148a01b93505bb43e6b619a77b54cdc67f
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Sun Jun 21 18:26:09 2026 -0700

    [SPARK-57589][CORE] Support `spark.ui.contentSecurityPolicy.enabled`
    
    ### What changes were proposed in this pull request?
    
    This PR adds a new config `spark.ui.contentSecurityPolicy.enabled` (default 
`false`) that gates the new feature, `Content-Security-Policy` response header 
introduced in SPARK-55252 and SPARK-55522. The header is now only set when the 
config is enabled.
    
    ### Why are the changes needed?
    
    To make the Spark UI `Content-Security-Policy` header opt-in, disabling it 
by default in Apache Spark 4.2.0.
    
    After testing this feature in Apache Spark 4.2.0, we can enable it at 4.3.0.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No behavior change because Spark UI no longer sends the 
`Content-Security-Policy` header at Apache Spark 4.2.0.  Previously, it was a 
breaking change.
    
    ### How was this patch tested?
    
    Pass the CIs.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.8)
    
    Closes #56642 from dongjoon-hyun/dongjoon/eloquent-yonath-4682a1.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 85fdee9f12ac93903e4a4bda01ea930d9ab3fd51)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit adaaa9d01b2dbd2afdbbb2a1be855a14511d056a)
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../main/scala/org/apache/spark/internal/config/UI.scala | 10 ++++++++++
 .../scala/org/apache/spark/ui/HttpSecurityFilter.scala   | 10 ++++++----
 .../org/apache/spark/ui/HttpSecurityFilterSuite.scala    | 16 +++++++++++++++-
 docs/security.md                                         | 10 ++++++++++
 4 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/core/src/main/scala/org/apache/spark/internal/config/UI.scala 
b/core/src/main/scala/org/apache/spark/internal/config/UI.scala
index 20c99794cd21..2e83c1f64f2e 100644
--- a/core/src/main/scala/org/apache/spark/internal/config/UI.scala
+++ b/core/src/main/scala/org/apache/spark/internal/config/UI.scala
@@ -136,6 +136,16 @@ private[spark] object UI {
     .stringConf
     .createOptional
 
+  val UI_CONTENT_SECURITY_POLICY_ENABLED =
+    ConfigBuilder("spark.ui.contentSecurityPolicy.enabled")
+      .doc("Whether to set the HTTP Content-Security-Policy (CSP) response 
header for the " +
+        "Spark UI. When enabled, CSP restricts the sources from which the 
browser is allowed " +
+        "to load resources, providing defense-in-depth against cross-site 
scripting (XSS).")
+      .version("4.2.0")
+      .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+      .booleanConf
+      .createWithDefault(false)
+
   val UI_REQUEST_HEADER_SIZE = ConfigBuilder("spark.ui.requestHeaderSize")
     .doc("Value for HTTP request header size in bytes.")
     .version("2.2.3")
diff --git a/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala 
b/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala
index a7e0bb683dd0..d06f2162d2f6 100644
--- a/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala
+++ b/core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala
@@ -51,10 +51,12 @@ private class HttpSecurityFilter(
 
     val cspNonce = CspNonce.generate()
     try {
-      hres.setHeader("Content-Security-Policy",
-        s"default-src 'self'; script-src 'self' 'nonce-$cspNonce'; " +
-        s"style-src 'self' 'unsafe-inline'; img-src 'self' data:; " +
-        s"object-src 'none'; base-uri 'self';")
+      if (conf.get(UI_CONTENT_SECURITY_POLICY_ENABLED)) {
+        hres.setHeader("Content-Security-Policy",
+          s"default-src 'self'; script-src 'self' 'nonce-$cspNonce'; " +
+          s"style-src 'self' 'unsafe-inline'; img-src 'self' data:; " +
+          s"object-src 'none'; base-uri 'self';")
+      }
 
       val requestUser = hreq.getRemoteUser()
 
diff --git 
a/core/src/test/scala/org/apache/spark/ui/HttpSecurityFilterSuite.scala 
b/core/src/test/scala/org/apache/spark/ui/HttpSecurityFilterSuite.scala
index 9965751c42cc..776f2e4496cc 100644
--- a/core/src/test/scala/org/apache/spark/ui/HttpSecurityFilterSuite.scala
+++ b/core/src/test/scala/org/apache/spark/ui/HttpSecurityFilterSuite.scala
@@ -119,6 +119,7 @@ class HttpSecurityFilterSuite extends SparkFunSuite {
       .set(UI_X_XSS_PROTECTION, "xssProtection")
       .set(UI_X_CONTENT_TYPE_OPTIONS, true)
       .set(UI_STRICT_TRANSPORT_SECURITY, "tsec")
+      .set(UI_CONTENT_SECURITY_POLICY_ENABLED, true)
     val secMgr = new SecurityManager(conf)
     val req = mockRequest()
     val res = mock(classOf[HttpServletResponse])
@@ -149,6 +150,19 @@ class HttpSecurityFilterSuite extends SparkFunSuite {
     }
   }
 
+  test("Content-Security-Policy header is not set by default") {
+    val conf = new SparkConf(false)
+    val secMgr = new SecurityManager(conf)
+    val req = mockRequest()
+    val res = mock(classOf[HttpServletResponse])
+    val chain = mock(classOf[FilterChain])
+
+    val filter = new HttpSecurityFilter(conf, secMgr)
+    filter.doFilter(req, res, chain)
+
+    verify(res, times(0)).setHeader(meq("Content-Security-Policy"), any())
+  }
+
   test("doAs impersonation") {
     val conf = new SparkConf(false)
       .set(ACLS_ENABLE, true)
@@ -186,7 +200,7 @@ class HttpSecurityFilterSuite extends SparkFunSuite {
   }
 
   test("CSP nonce is available during chain.doFilter and cleared after") {
-    val conf = new SparkConf(false)
+    val conf = new SparkConf(false).set(UI_CONTENT_SECURITY_POLICY_ENABLED, 
true)
     val secMgr = new SecurityManager(conf)
     val req = mockRequest()
     val res = mock(classOf[HttpServletResponse])
diff --git a/docs/security.md b/docs/security.md
index 484017df84fb..b3743d9a1b0a 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -803,6 +803,16 @@ Security.
   </td>
   <td>2.3.0</td>
 </tr>
+<tr>
+  <td><code>spark.ui.contentSecurityPolicy.enabled</code></td>
+  <td><code>false</code></td>
+  <td>
+    When enabled, the Content-Security-Policy (CSP) HTTP response header is 
set for the Spark UI,
+    restricting the sources from which the browser is allowed to load 
resources as a
+    defense-in-depth measure against cross-site scripting (XSS).
+  </td>
+  <td>4.2.0</td>
+</tr>
 </table>
 
 


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

Reply via email to