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

pierrejeambrun pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 3db8e458893 [v3-3-test] reject backslash-after-scheme urls in 
is_safe_url (#70215) (#70515)
3db8e458893 is described below

commit 3db8e458893a88e61a9305e7a9a48690e8fffeda
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Jul 27 17:01:11 2026 +0200

    [v3-3-test] reject backslash-after-scheme urls in is_safe_url (#70215) 
(#70515)
    
    (cherry picked from commit eeeb6f02a88791f3c1f9b14c92e3b0b70c5d38a0)
    
    Co-authored-by: Samina <[email protected]>
---
 airflow-core/src/airflow/api_fastapi/core_api/security.py     | 9 +++++----
 airflow-core/tests/unit/api_fastapi/core_api/test_security.py | 8 ++++++++
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/core_api/security.py 
b/airflow-core/src/airflow/api_fastapi/core_api/security.py
index 5f85b68e6b1..720423797b5 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -1007,12 +1007,13 @@ def is_safe_url(target_url: str, request: Request | 
None = None) -> bool:
 
     # According to WHATWG for http/https /// is interpreted as // whereas 
urllib doesnt
     # this leads to an inconsistency where python returns a target url with 
/// as a valid url
-    # The same thing also happens with \ where under WHATWG \ are translated 
to /
-    target_url = unquote(target_url).strip()
-    if target_url.startswith(("//", "/\\", "\\/", "\\\\")):
+    # The same thing also happens with \ where under WHATWG \ are translated 
to /, including
+    # after a scheme, so "https:\\host" is an authority for a browser but a 
path for urllib.
+    target_url = unquote(target_url).strip().replace("\\", "/")
+    if target_url.startswith("//"):
         return False
     for base_url, parsed_base in parsed_bases:
-        parsed_target = urlparse(urljoin(base_url, unquote(target_url)))  # 
Resolves relative URLs
+        parsed_target = urlparse(urljoin(base_url, target_url))  # Resolves 
relative URLs
 
         base_path = parsed_base.path or "/"
         target_path = parsed_target.path or "/"
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py 
b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
index d7614f82539..8824969f976 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
@@ -632,6 +632,14 @@ class TestFastApiSecurity:
             ("\\\\some_netlock.com/prefix", False),
             # encoded url
             ("%5C%5C%5C%5Csome_netlock.com/prefix", False),
+            # \ after the scheme, which a browser reads as the start of the 
authority
+            ("https:\\\\some_netlock.com", False),
+            ("https:/\\some_netlock.com", False),
+            ("https:\\/some_netlock.com", False),
+            ("https%3A%5C%5Csome_netlock.com", False),
+            # a single leading \ still resolves to a same-origin path
+            ("\\some_page", True),
+            ("/some_page", True),
         ],
     )
     def test_is_safe_url_without_prefix(self, url, expected_is_safe):

Reply via email to