dongjoon-hyun commented on code in PR #57549:
URL: https://github.com/apache/spark/pull/57549#discussion_r3677325314


##########
core/src/main/scala/org/apache/spark/ui/UIUtils.scala:
##########
@@ -211,7 +211,19 @@ private[spark] object UIUtils extends Logging {
       request: HttpServletRequest,
       basePath: String = "",
       resource: String = ""): String = {
-    uiRoot(request) + basePath + resource
+    val root = uiRoot(request).stripSuffix("/")
+    val hasScheme = basePath.startsWith("http://";) || 
basePath.startsWith("https://";)
+    val cleanBase = if (hasScheme || basePath.startsWith("/") || 
basePath.isEmpty) {
+      basePath
+    } else {
+      "/" + basePath
+    }
+    val cleanResource = if (resource.startsWith("/") || resource.isEmpty) {
+      resource
+    } else {
+      "/" + resource
+    }

Review Comment:
   This normalization looks out of scope for this fix. Checking all current 
call sites,
   `basePath` and `resource` are always either empty or already start with `/`, 
so the
   slash-insertion is a no-op today — and this is a shared path that affects 
every UI
   (YARN, K8s, History Server), not just Standalone reverse proxy.
   
   The suggested `hasScheme` branch is also buggy as written: no caller passes 
an absolute URL as
   `basePath`, but if one did while `root` is non-empty, `s"$root$cleanBase"` 
would produce
   a broken URL like `/proxy/app-1http://...`.
   
   Please recover the original code.



##########
core/src/main/scala/org/apache/spark/ui/HttpSecurityFilter.scala:
##########
@@ -49,9 +49,12 @@ private class HttpSecurityFilter(
     val hres = res.asInstanceOf[HttpServletResponse]
     hres.setHeader("Cache-Control", "no-cache, no-store, must-revalidate")
 
+    val isProxyRequest = 
Option(hreq.getPathInfo).exists(_.startsWith("/proxy/")) ||
+      Option(hreq.getRequestURI).exists(_.contains("/proxy/"))

Review Comment:
   The current check is spoofable: `getRequestURI.contains("/proxy/")` matches 
any URL
   containing that substring (e.g. `/jobs/proxy/x`, `/history/proxy/...`), so 
an attacker-chosen
   URL can disable CSP on any UI (driver, Worker, History Server) — not just 
the Master's proxy.
   Meanwhile `getPathInfo.startsWith("/proxy/")` never matches for the actual 
proxy handler,
   because it is mounted with `contextPath="/proxy"`, so its `pathInfo` is 
`/app-123/...`.
   
   Checking the servlet context path is exact and cannot be forged by crafted 
request paths:
   
   ```suggestion
       val isProxyRequest = hreq.getContextPath == "/proxy"
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to