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

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


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new 29ce2576563 [v3-2-test] Updates the is_url_safe method to reject urls 
with /// (#65557) (#65737)
29ce2576563 is described below

commit 29ce257656397833d14bbd03acd90984d0a2e2f4
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Apr 24 12:55:26 2026 +0200

    [v3-2-test] Updates the is_url_safe method to reject urls with /// (#65557) 
(#65737)
    
    * Updates the is_url_safe method to reject urls with ///
    
    JS's URL constructor sticks to WHATWG's Url parsing for
    special schemes ref: https://url.spec.whatwg.org/#url-parsing
    
    Due to which a host like ///example.com is a valid url in JS
    and resolves to http(s)://example.com if the base url is a http(s)
    URL.
    
    The same thing happens with \.
    
    * Adds unit tests for is_safe_url
    (cherry picked from commit 555f35576e3143dec65a7fb9acd95891841b45f2)
    
    Co-authored-by: Aritra Basu <[email protected]>
---
 airflow-core/src/airflow/api_fastapi/core_api/security.py |  6 ++++++
 .../tests/unit/api_fastapi/core_api/test_security.py      | 15 +++++++++++++++
 2 files changed, 21 insertions(+)

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 774cfa9ed6a..941a4c418f5 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -779,6 +779,12 @@ def is_safe_url(target_url: str, request: Request | None = 
None) -> bool:
         # Can't enforce any security check.
         return True
 
+    # 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(("//", "/\\", "\\/", "\\\\")):
+        return False
     for base_url, parsed_base in parsed_bases:
         parsed_target = urlparse(urljoin(base_url, unquote(target_url)))  # 
Resolves relative URLs
 
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 e5cb5f17cf9..9b8a3abd762 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
@@ -413,6 +413,21 @@ class TestFastApiSecurity:
         request.base_url = "https://requesting_server_base_url.com/prefix2";
         assert is_safe_url(url, request=request) == expected_is_safe
 
+    @pytest.mark.parametrize(
+        ("url", "expected_is_safe"),
+        [
+            # Using \ or /// to escape host check
+            ("///some_netlock.com/prefix", False),
+            ("\\\\some_netlock.com/prefix", False),
+            # encoded url
+            ("%5C%5C%5C%5Csome_netlock.com/prefix", False),
+        ],
+    )
+    def test_is_safe_url_without_prefix(self, url, expected_is_safe):
+        request = Mock()
+        request.base_url = "https://requesting_server_base_url.com/";
+        assert is_safe_url(url, request=request) == expected_is_safe
+
     @pytest.mark.parametrize(
         ("url", "expected_is_safe"),
         [

Reply via email to