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

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

commit e9d623618a353e19234f109c80c0a0914697db56
Author: Kevin Yang <[email protected]>
AuthorDate: Wed Sep 17 11:45:57 2025 -0400

    Fix redirection to 'next' url raises an unsafe error (#55704)
    
    * precommit fix
    
    * add test case for base_url unset
    
    * fix the check logic failed for relative url by always unquote
    
    (cherry picked from commit 9bc58a20e58a8b8eee9cf2fbe679f40172abc55d)
---
 .../src/airflow/api_fastapi/core_api/security.py   |  4 ++--
 .../unit/api_fastapi/core_api/test_security.py     | 23 ++++++++++++++++++++++
 2 files changed, 25 insertions(+), 2 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 84bd0ccdd29..50ff119cbbc 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -19,7 +19,7 @@ from __future__ import annotations
 from collections.abc import Callable
 from pathlib import Path
 from typing import TYPE_CHECKING, Annotated, cast
-from urllib.parse import ParseResult, urljoin, urlparse
+from urllib.parse import ParseResult, unquote, urljoin, urlparse
 
 from fastapi import Depends, HTTPException, Request, status
 from fastapi.security import HTTPBearer, OAuth2PasswordBearer
@@ -484,7 +484,7 @@ def is_safe_url(target_url: str, request: Request | None = 
None) -> bool:
         return True
 
     for base_url, parsed_base in parsed_bases:
-        parsed_target = urlparse(urljoin(base_url, target_url))  # Resolves 
relative URLs
+        parsed_target = urlparse(urljoin(base_url, unquote(target_url)))  # 
Resolves relative URLs
 
         target_path = Path(parsed_target.path).resolve()
 
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 29efa6eae0e..1be6e8b5667 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
@@ -154,6 +154,12 @@ class TestFastApiSecurity:
             ("/some_other_page/", False),
             # traversal, escaping the `prefix` folder
             ("/../../../../some_page?with_param=3", False),
+            # encoded url
+            ("https%3A%2F%2Frequesting_server_base_url.com%2Fprefix2", True),
+            ("https%3A%2F%2Fserver_base_url.com%2Fprefix", True),
+            
("https%3A%2F%2Fsome_netlock.com%2Fprefix%2Fsome_page%3Fwith_param%3D3", False),
+            
("https%3A%2F%2Frequesting_server_base_url.com%2Fprefix2%2Fsub_path", True),
+            ("%2F..%2F..%2F..%2F..%2Fsome_page%3Fwith_param%3D3", False),
         ],
     )
     @conf_vars({("api", "base_url"): "https://server_base_url.com/prefix"})
@@ -162,6 +168,23 @@ 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",
+        [
+            ("https://server_base_url.com/prefix";, False),
+            ("https://requesting_server_base_url.com/prefix2";, True),
+            ("prefix/some_other", False),
+            ("https%3A%2F%2Fserver_base_url.com%2Fprefix", False),
+            ("https%3A%2F%2Frequesting_server_base_url.com%2Fprefix2", True),
+            
("https%3A%2F%2Frequesting_server_base_url.com%2Fprefix2%2Fsub_path", True),
+            ("%2F..%2F..%2F..%2F..%2Fsome_page%3Fwith_param%3D3", False),
+        ],
+    )
+    def test_is_safe_url_with_base_url_unset(self, url, expected_is_safe):
+        request = Mock()
+        request.base_url = "https://requesting_server_base_url.com/prefix2";
+        assert is_safe_url(url, request=request) == expected_is_safe
+
     @pytest.mark.db_test
     @pytest.mark.parametrize(
         "team_name",

Reply via email to